]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Controllers/Crud/MembersController.php
Configure members list displayed fields; closes #979
[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\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]);
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 (isset($post['group_filter'])
634 && $post['group_filter'] > 0
635 ) {
636 $filters->group_filter = (int)$post['group_filter'];
637 }
638 //number of rows to show
639 if (isset($post['nbshow'])) {
640 $filters->show = $post['nbshow'];
641 }
642
643 if (isset($post['advanced_filtering'])) {
644 if (!$filters instanceof AdvancedMembersList) {
645 $filters = new AdvancedMembersList($filters);
646 }
647 //Advanced filters
648 $filters->reinit();
649 unset($post['advanced_filtering']);
650 $freed = false;
651 foreach ($post as $k => $v) {
652 if (strpos($k, 'free_', 0) === 0) {
653 if (!$freed) {
654 $i = 0;
655 foreach ($post['free_field'] as $f) {
656 if (trim($f) !== ''
657 && trim($post['free_text'][$i]) !== ''
658 ) {
659 $fs_search = $post['free_text'][$i];
660 $log_op
661 = (int)$post['free_logical_operator'][$i];
662 $qry_op
663 = (int)$post['free_query_operator'][$i];
664 $type = (int)$post['free_type'][$i];
665 $fs = array(
666 'idx' => $i,
667 'field' => $f,
668 'type' => $type,
669 'search' => $fs_search,
670 'log_op' => $log_op,
671 'qry_op' => $qry_op
672 );
673 $filters->free_search = $fs;
674 }
675 $i++;
676 }
677 $freed = true;
678 }
679 } elseif ($k == 'groups_search') {
680 $i = 0;
681 $filters->groups_search_log_op = (int)$post['groups_logical_operator'];
682 foreach ($post['groups_search'] as $g) {
683 if (trim($g) !== '') {
684 $gs = array(
685 'idx' => $i,
686 'group' => $g
687 );
688 $filters->groups_search = $gs;
689 }
690 $i++;
691 }
692 } else {
693 switch ($k) {
694 case 'contrib_min_amount':
695 case 'contrib_max_amount':
696 if (trim($v) !== '') {
697 $v = (float)$v;
698 } else {
699 $v = null;
700 }
701 break;
702 }
703 $filters->$k = $v;
704 }
705 }
706 }
707 }
708
709 if (isset($post['savesearch'])) {
710 return $response
711 ->withStatus(301)
712 ->withHeader(
713 'Location',
714 $this->router->pathFor(
715 'saveSearch',
716 $post
717 )
718 );
719 }
720
721 $this->session->filter_members = $filters;
722
723 return $response
724 ->withStatus(301)
725 ->withHeader('Location', $this->router->pathFor('members'));
726 }
727
728 /**
729 * Advanced search page
730 *
731 * @param Request $request PSR Request
732 * @param Response $response PSR Response
733 *
734 * @return Response
735 */
736 public function advancedSearch(Request $request, Response $response) :Response
737 {
738 if (isset($this->session->filter_members)) {
739 $filters = $this->session->filter_members;
740 if (!$filters instanceof AdvancedMembersList) {
741 $filters = new AdvancedMembersList($filters);
742 }
743 } else {
744 $filters = new AdvancedMembersList();
745 }
746
747 $groups = new Groups($this->zdb, $this->login);
748 $groups_list = $groups->getList();
749
750 //we want only visibles fields
751 $fields = $this->members_fields;
752 $fc = $this->fields_config;
753 $visibles = $fc->getVisibilities();
754 $access_level = $this->login->getAccessLevel();
755
756 //remove not searchable fields
757 unset($fields['mdp_adh']);
758
759 foreach ($fields as $k => $f) {
760 if ($visibles[$k] == FieldsConfig::NOBODY ||
761 ($visibles[$k] == FieldsConfig::ADMIN &&
762 $access_level < Authentication::ACCESS_ADMIN) ||
763 ($visibles[$k] == FieldsConfig::STAFF &&
764 $access_level < Authentication::ACCESS_STAFF) ||
765 ($visibles[$k] == FieldsConfig::MANAGER &&
766 $access_level < Authentication::ACCESS_MANAGER)
767 ) {
768 unset($fields[$k]);
769 }
770 }
771
772 //add status label search
773 if ($pos = array_search(Status::PK, array_keys($fields))) {
774 $fields = array_slice($fields, 0, $pos, true) +
775 ['status_label' => ['label' => _T('Status label')]] +
776 array_slice($fields, $pos, count($fields) -1, true);
777 }
778
779 //dynamic fields
780 $deps = array(
781 'picture' => false,
782 'groups' => false,
783 'dues' => false,
784 'parent' => false,
785 'children' => false,
786 'dynamics' => false
787 );
788 $member = new Adherent($this->zdb, $this->login->login, $deps);
789 $adh_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $member);
790
791 $contrib = new Contribution($this->zdb, $this->login);
792 $contrib_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $contrib);
793
794 //Status
795 $statuts = new Status($this->zdb);
796
797 //Contributions types
798 $ct = new ContributionsTypes($this->zdb);
799
800 //Payments types
801 $ptypes = new PaymentTypes(
802 $this->zdb,
803 $this->preferences,
804 $this->login
805 );
806 $ptlist = $ptypes->getList();
807
808 $filters->setViewCommonsFilters($this->preferences, $this->view->getSmarty());
809
810 // display page
811 $this->view->render(
812 $response,
813 'advanced_search.tpl',
814 array(
815 'page_title' => _T("Advanced search"),
816 'filter_groups_options' => $groups_list,
817 'search_fields' => $fields,
818 'adh_dynamics' => $adh_dynamics->getFields(),
819 'contrib_dynamics' => $contrib_dynamics->getFields(),
820 'statuts' => $statuts->getList(),
821 'contributions_types' => $ct->getList(),
822 'filters' => $filters,
823 'payments_types' => $ptlist
824 )
825 );
826 return $response;
827 }
828
829 /**
830 * Members list for ajax
831 *
832 * @param Request $request PSR Request
833 * @param Response $response PSR Response
834 * @param array $args Request arguments
835 *
836 * @return Response
837 */
838 public function ajaxList(Request $request, Response $response, array $args = []) :Response
839 {
840 $post = $request->getParsedBody();
841
842 if (isset($this->session->ajax_members_filters)) {
843 $filters = $this->session->ajax_members_filters;
844 } else {
845 $filters = new MembersList();
846 }
847
848 if (isset($args['option']) && $args['option'] == 'page') {
849 $filters->current_page = (int)$args['value'];
850 }
851
852 //numbers of rows to display
853 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
854 $filters->show = $post['nbshow'];
855 }
856
857 $members = new Members($filters);
858 if (!$this->login->isAdmin() && !$this->login->isStaff()) {
859 if ($this->login->isGroupManager()) {
860 $members_list = $members->getManagedMembersList(true);
861 } else {
862 Analog::log(
863 str_replace(
864 ['%id', '%login'],
865 [$this->login->id, $this->login->login],
866 'Trying to list group members without access from #%id (%login)'
867 ),
868 Analog::ERROR
869 );
870 throw new \Exception('Access denied.');
871 }
872 } else {
873 $members_list = $members->getMembersList(true);
874 }
875
876 //assign pagination variables to the template and add pagination links
877 $filters->setSmartyPagination($this->router, $this->view->getSmarty(), false);
878
879 $this->session->ajax_members_filters = $filters;
880
881 $selected_members = null;
882 $unreachables_members = null;
883 if (!isset($post['from'])) {
884 $mailing = $this->session->mailing;
885 if (!isset($post['members'])) {
886 $selected_members = $mailing->recipients;
887 $unreachables_members = $mailing->unreachables;
888 } else {
889 $m = new Members();
890 $selected_members = $m->getArrayList($post['members']);
891 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
892 $unreachables_members = $m->getArrayList($post['unreachables']);
893 }
894 }
895 } else {
896 switch ($post['from']) {
897 case 'groups':
898 if (!isset($post['gid'])) {
899 Analog::log(
900 'Trying to list group members with no group id provided',
901 Analog::ERROR
902 );
903 throw new \Exception('A group id is required.');
904 exit(0);
905 }
906 if (!isset($post['members'])) {
907 $group = new Group((int)$post['gid']);
908 $selected_members = array();
909 if (!isset($post['mode']) || $post['mode'] == 'members') {
910 $selected_members = $group->getMembers();
911 } elseif ($post['mode'] == 'managers') {
912 $selected_members = $group->getManagers();
913 } else {
914 Analog::log(
915 'Trying to list group members with unknown mode',
916 Analog::ERROR
917 );
918 throw new \Exception('Unknown mode.');
919 exit(0);
920 }
921 } else {
922 $m = new Members();
923 $selected_members = $m->getArrayList($post['members']);
924 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
925 $unreachables_members = $m->getArrayList($post['unreachables']);
926 }
927 }
928 break;
929 case 'attach':
930 if (!isset($post['id_adh'])) {
931 throw new \RuntimeException(
932 'Current selected member must be excluded while attaching!'
933 );
934 exit(0);
935 }
936 break;
937 }
938 }
939
940 $params = [
941 'filters' => $filters,
942 'members_list' => $members_list,
943 'selected_members' => $selected_members,
944 'unreachables_members' => $unreachables_members
945 ];
946
947 if (isset($post['multiple'])) {
948 $params['multiple'] = true;
949 }
950
951 if (isset($post['gid'])) {
952 $params['the_id'] = $post['gid'];
953 }
954
955 if (isset($post['id_adh'])) {
956 $params['excluded'] = $post['id_adh'];
957 }
958
959 // display page
960 $this->view->render(
961 $response,
962 'ajax_members.tpl',
963 $params
964 );
965 return $response;
966 }
967
968 /**
969 * Batch actions handler
970 *
971 * @param Request $request PSR Request
972 * @param Response $response PSR Response
973 *
974 * @return Response
975 */
976 public function handleBatch(Request $request, Response $response) :Response
977 {
978 $post = $request->getParsedBody();
979
980 if (isset($post['member_sel'])) {
981 if (isset($this->session->filter_members)) {
982 $filters = $this->session->filter_members;
983 } else {
984 $filters = new MembersList();
985 }
986
987 $filters->selected = $post['member_sel'];
988 $this->session->filter_members = $filters;
989
990 if (isset($post['cards'])) {
991 return $response
992 ->withStatus(301)
993 ->withHeader('Location', $this->router->pathFor('pdf-members-cards'));
994 }
995
996 if (isset($post['labels'])) {
997 return $response
998 ->withStatus(301)
999 ->withHeader('Location', $this->router->pathFor('pdf-members-labels'));
1000 }
1001
1002 if (isset($post['mailing'])) {
1003 return $response
1004 ->withStatus(301)
1005 ->withHeader('Location', $this->router->pathFor('mailing') . '?mailing_new=new');
1006 }
1007
1008 if (isset($post['attendance_sheet'])) {
1009 return $response
1010 ->withStatus(301)
1011 ->withHeader('Location', $this->router->pathFor('attendance_sheet_details'));
1012 }
1013
1014 if (isset($post['csv'])) {
1015 return $response
1016 ->withStatus(301)
1017 ->withHeader('Location', $this->router->pathFor('csv-memberslist'));
1018 }
1019
1020 if (isset($post['delete'])) {
1021 return $response
1022 ->withStatus(301)
1023 ->withHeader('Location', $this->router->pathFor('removeMembers'));
1024 }
1025
1026 if (isset($post['masschange'])) {
1027 return $response
1028 ->withStatus(301)
1029 ->withHeader('Location', $this->router->pathFor('masschangeMembers'));
1030 }
1031
1032 throw new \RuntimeException('Does not know what to batch :(');
1033 } else {
1034 $this->flash->addMessage(
1035 'error_detected',
1036 _T("No member was selected, please check at least one name.")
1037 );
1038
1039 return $response
1040 ->withStatus(301)
1041 ->withHeader('Location', $this->router->pathFor('members'));
1042 }
1043 }
1044
1045 // /CRUD - Read
1046 // CRUD - Update
1047
1048 /**
1049 * Edit page
1050 *
1051 * @param Request $request PSR Request
1052 * @param Response $response PSR Response
1053 * @param array $args Request arguments
1054 *
1055 * @return Response
1056 */
1057 public function edit(Request $request, Response $response, array $args = []) :Response
1058 {
1059 $action = $args['action'];
1060 $id = null;
1061 if (isset($args['id'])) {
1062 $id = (int)$args['id'];
1063 }
1064
1065 if ($action === 'edit' && $id === null) {
1066 throw new \RuntimeException(
1067 _T("Member ID cannot ben null calling edit route!")
1068 );
1069 } elseif ($action === 'add' && $id !== null) {
1070 return $response
1071 ->withStatus(301)
1072 ->withHeader('Location', $this->router->pathFor('editmember', ['action' => 'add']));
1073 }
1074 $deps = array(
1075 'picture' => true,
1076 'groups' => true,
1077 'dues' => true,
1078 'parent' => true,
1079 'children' => true,
1080 'dynamics' => true
1081 );
1082
1083 if ($this->session->member !== null) {
1084 $member = $this->session->member;
1085 $this->session->member = null;
1086 } else {
1087 $member = new Adherent($this->zdb, $id, $deps);
1088 }
1089
1090 if ($id !== null) {
1091 $member->load($id);
1092 if (!$member->canEdit($this->login)) {
1093 $this->flash->addMessage(
1094 'error_detected',
1095 _T("You do not have permission for requested URL.")
1096 );
1097
1098 return $response
1099 ->withStatus(403)
1100 ->withHeader(
1101 'Location',
1102 $this->router->pathFor('me')
1103 );
1104 }
1105 } else {
1106 if ($member->id != $id) {
1107 $member->load($this->login->id);
1108 }
1109 }
1110
1111 // flagging required fields
1112 $fc = $this->fields_config;
1113
1114 // password required if we create a new member
1115 if ($member->id != '') {
1116 $fc->setNotRequired('mdp_adh');
1117 }
1118
1119 //handle requirements for parent fields
1120 $parent_fields = $member->getParentFields();
1121 $tpl_parent_fields = []; //for JS when detaching
1122 foreach ($parent_fields as $field) {
1123 if ($fc->isRequired($field)) {
1124 $tpl_parent_fields[] = $field;
1125 if ($member->hasParent()) {
1126 $fc->setNotRequired($field);
1127 }
1128 }
1129 }
1130
1131 // flagging required fields invisible to members
1132 if ($this->login->isAdmin() || $this->login->isStaff()) {
1133 $fc->setNotRequired('activite_adh');
1134 $fc->setNotRequired('id_statut');
1135 }
1136
1137 // template variable declaration
1138 $title = _T("Member Profile");
1139 if ($member->id != '') {
1140 $title .= ' (' . _T("modification") . ')';
1141 } else {
1142 $title .= ' (' . _T("creation") . ')';
1143 }
1144
1145 //Status
1146 $statuts = new Status($this->zdb);
1147
1148 //Groups
1149 $groups = new Groups($this->zdb, $this->login);
1150 $groups_list = $groups->getSimpleList(true);
1151
1152 $form_elements = $fc->getFormElements(
1153 $this->login,
1154 $member->id == ''
1155 );
1156
1157 // members
1158 $m = new Members();
1159 $id = null;
1160 if ($member->hasParent()) {
1161 $id = ($member->parent instanceof Adherent ? $member->parent->id : $member->parent);
1162 }
1163 $members = $m->getSelectizedMembers(
1164 $this->zdb,
1165 $id
1166 );
1167
1168 $route_params['members'] = [
1169 'filters' => $m->getFilters(),
1170 'count' => $m->getCount()
1171 ];
1172
1173 if (count($members)) {
1174 $route_params['members']['list'] = $members;
1175 }
1176
1177 // display page
1178 $this->view->render(
1179 $response,
1180 'member.tpl',
1181 array(
1182 'parent_tpl' => 'page.tpl',
1183 'autocomplete' => true,
1184 'page_title' => $title,
1185 'member' => $member,
1186 'self_adh' => false,
1187 // pseudo random int
1188 'time' => time(),
1189 'titles_list' => Titles::getList($this->zdb),
1190 'statuts' => $statuts->getList(),
1191 'groups' => $groups_list,
1192 'fieldsets' => $form_elements['fieldsets'],
1193 'hidden_elements' => $form_elements['hiddens'],
1194 'parent_fields' => $tpl_parent_fields
1195 ) + $route_params
1196 );
1197 return $response;
1198 }
1199
1200 /**
1201 * Edit action
1202 *
1203 * @param Request $request PSR Request
1204 * @param Response $response PSR Response
1205 * @param array $args Request arguments
1206 *
1207 * @return Response
1208 */
1209 public function doEdit(Request $request, Response $response, array $args = []) :Response
1210 {
1211 return $this->store($request, $response, $args);
1212 }
1213
1214 /**
1215 * Massive change page
1216 *
1217 * @param Request $request PSR Request
1218 * @param Response $response PSR Response
1219 * @param array $args Request arguments
1220 *
1221 * @return Response
1222 */
1223 public function massChange(Request $request, Response $response, array $args = []) :Response
1224 {
1225 $filters = $this->session->filter_members;
1226
1227 $data = [
1228 'id' => $filters->selected,
1229 'redirect_uri' => $this->router->pathFor('members')
1230 ];
1231
1232 $fc = $this->fields_config;
1233 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1234
1235 //dynamic fields
1236 $deps = array(
1237 'picture' => false,
1238 'groups' => false,
1239 'dues' => false,
1240 'parent' => false,
1241 'children' => false,
1242 'dynamics' => false
1243 );
1244 $member = new Adherent($this->zdb, null, $deps);
1245
1246 //Status
1247 $statuts = new Status($this->zdb);
1248
1249 // display page
1250 $this->view->render(
1251 $response,
1252 'mass_change_members.tpl',
1253 array(
1254 'mode' => $request->isXhr() ? 'ajax' : '',
1255 'page_title' => str_replace(
1256 '%count',
1257 count($data['id']),
1258 _T('Mass change %count members')
1259 ),
1260 'form_url' => $this->router->pathFor('masschangeMembersReview'),
1261 'cancel_uri' => $this->router->pathFor('members'),
1262 'data' => $data,
1263 'member' => $member,
1264 'fieldsets' => $form_elements['fieldsets'],
1265 'titles_list' => Titles::getList($this->zdb),
1266 'statuts' => $statuts->getList(),
1267 'require_mass' => true
1268 )
1269 );
1270 return $response;
1271 }
1272
1273 /**
1274 * Massive changes validation page
1275 *
1276 * @param Request $request PSR Request
1277 * @param Response $response PSR Response
1278 * @param array $args Request arguments
1279 *
1280 * @return Response
1281 */
1282 public function validateMassChange(Request $request, Response $response, array $args = []) :Response
1283 {
1284 $post = $request->getParsedBody();
1285
1286 if (!isset($post['confirm'])) {
1287 $this->flash->addMessage(
1288 'error_detected',
1289 _T("Mass changes has not been confirmed!")
1290 );
1291 } else {
1292 //we want only visibles fields
1293 $fc = $this->fields_config;
1294 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1295
1296 $changes = [];
1297 foreach ($form_elements['fieldsets'] as $form_element) {
1298 foreach ($form_element->elements as $field) {
1299 if (isset($post[$field->field_id]) && isset($post['mass_' . $field->field_id])) {
1300 $changes[$field->field_id] = [
1301 'label' => $field->label,
1302 'value' => $post[$field->field_id]
1303 ];
1304 }
1305 }
1306 }
1307 }
1308
1309 $filters = $this->session->filter_members;
1310 $data = [
1311 'id' => $filters->selected,
1312 'redirect_uri' => $this->router->pathFor('members')
1313 ];
1314
1315 //Status
1316 $statuts = new Status($this->zdb);
1317
1318 // display page
1319 $this->view->render(
1320 $response,
1321 'mass_change_members.tpl',
1322 array(
1323 'mode' => $request->isXhr() ? 'ajax' : '',
1324 'page_title' => str_replace(
1325 '%count',
1326 count($data['id']),
1327 _T('Review mass change %count members')
1328 ),
1329 'form_url' => $this->router->pathFor('massstoremembers'),
1330 'cancel_uri' => $this->router->pathFor('members'),
1331 'data' => $data,
1332 'titles_list' => Titles::getList($this->zdb),
1333 'statuts' => $statuts->getList(),
1334 'changes' => $changes
1335 )
1336 );
1337 return $response;
1338 }
1339
1340 /**
1341 * Do massive changes
1342 *
1343 * @param Request $request PSR Request
1344 * @param Response $response PSR Response
1345 * @param array $args Request arguments
1346 *
1347 * @return Response
1348 */
1349 public function doMassChange(Request $request, Response $response, array $args = []) :Response
1350 {
1351 $post = $request->getParsedBody();
1352 $redirect_url = $post['redirect_uri'];
1353 $error_detected = [];
1354 $mass = 0;
1355
1356 unset($post['redirect_uri']);
1357 if (!isset($post['confirm'])) {
1358 $error_detected[] = _T("Mass changes has not been confirmed!");
1359 } else {
1360 unset($post['confirm']);
1361 $ids = $post['id'];
1362 unset($post['id']);
1363
1364 $fc = $this->fields_config;
1365 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
1366 $disabled = $this->members_fields;
1367 foreach (array_keys($post) as $key) {
1368 $found = false;
1369 foreach ($form_elements['fieldsets'] as $fieldset) {
1370 if (isset($fieldset->elements[$key])) {
1371 $found = true;
1372 continue;
1373 }
1374 }
1375 if (!$found) {
1376 Analog::log(
1377 'Permission issue mass editing field ' . $key,
1378 Analog::WARNING
1379 );
1380 unset($post[$key]);
1381 } else {
1382 unset($disabled[$key]);
1383 }
1384 }
1385
1386 if (!count($post)) {
1387 $error_detected[] = _T("Nothing to do!");
1388 } else {
1389 foreach ($ids as $id) {
1390 $is_manager = !$this->login->isAdmin()
1391 && !$this->login->isStaff()
1392 && $this->login->isGroupManager();
1393 $deps = array(
1394 'picture' => false,
1395 'groups' => $is_manager,
1396 'dues' => false,
1397 'parent' => false,
1398 'children' => false,
1399 'dynamics' => false
1400 );
1401 $member = new Adherent($this->zdb, (int)$id, $deps);
1402 $member->setDependencies(
1403 $this->preferences,
1404 $this->members_fields,
1405 $this->history
1406 );
1407 if (!$member->canEdit($this->login)) {
1408 continue;
1409 }
1410
1411 $valid = $member->check($post, [], $disabled);
1412 if ($valid === true) {
1413 $done = $member->store();
1414 if (!$done) {
1415 $error_detected[] = _T("An error occurred while storing the member.");
1416 } else {
1417 ++$mass;
1418 }
1419 } else {
1420 $error_detected = array_merge($error_detected, $valid);
1421 }
1422 }
1423 }
1424 }
1425
1426 if ($mass == 0 && !count($error_detected)) {
1427 $error_detected[] = _T('Something went wront during mass edition!');
1428 } else {
1429 $this->flash->addMessage(
1430 'success_detected',
1431 str_replace(
1432 '%count',
1433 $mass,
1434 _T('%count members has been changed successfully!')
1435 )
1436 );
1437 }
1438
1439 if (count($error_detected) > 0) {
1440 foreach ($error_detected as $error) {
1441 $this->flash->addMessage(
1442 'error_detected',
1443 $error
1444 );
1445 }
1446 }
1447
1448 if (!$request->isXhr()) {
1449 return $response
1450 ->withStatus(301)
1451 ->withHeader('Location', $redirect_url);
1452 } else {
1453 return $response->withJson(
1454 [
1455 'success' => count($error_detected) === 0
1456 ]
1457 );
1458 }
1459 }
1460
1461 /**
1462 * Store
1463 *
1464 * @param Request $request PSR Request
1465 * @param Response $response PSR Response
1466 * @param array $args Request arguments
1467 *
1468 * @return Response
1469 */
1470 public function store(Request $request, Response $response, array $args = []) :Response
1471 {
1472 if (!$this->preferences->pref_bool_selfsubscribe && !$this->login->isLogged()) {
1473 return $response
1474 ->withStatus(301)
1475 ->withHeader('Location', $this->router->pathFor('slash'));
1476 }
1477
1478 $post = $request->getParsedBody();
1479 $deps = array(
1480 'picture' => true,
1481 'groups' => true,
1482 'dues' => true,
1483 'parent' => true,
1484 'children' => true,
1485 'dynamics' => true
1486 );
1487 $member = new Adherent($this->zdb, null, $deps);
1488 $member->setDependencies(
1489 $this->preferences,
1490 $this->members_fields,
1491 $this->history
1492 );
1493 if (isset($args['self'])) {
1494 //mark as self membership
1495 $member->setSelfMembership();
1496 }
1497
1498 $success_detected = [];
1499 $warning_detected = [];
1500 $error_detected = [];
1501
1502 // new or edit
1503 $adherent['id_adh'] = get_numeric_form_value('id_adh', '');
1504 if ($this->login->isAdmin() || $this->login->isStaff() || $this->login->isGroupManager()) {
1505 if ($adherent['id_adh']) {
1506 $member->load((int)$adherent['id_adh']);
1507 if (!$member->canEdit($this->login)) {
1508 //redirection should have been done before. Just throw an Exception.
1509 throw new \RuntimeException(
1510 str_replace(
1511 '%id',
1512 $member->id,
1513 'No right to store member #%id'
1514 )
1515 );
1516 }
1517 }
1518 } else {
1519 $member->load($this->login->id);
1520 $adherent['id_adh'] = $this->login->id;
1521 }
1522
1523 // flagging required fields
1524 $fc = $this->fields_config;
1525
1526 // password required if we create a new member
1527 if ($member->id != '') {
1528 $fc->setNotRequired('mdp_adh');
1529 }
1530
1531 if ($member->hasParent() && !isset($post['detach_parent'])
1532 || isset($post['parent_id']) && !empty($post['parent_id'])
1533 ) {
1534 $parent_fields = $member->getParentFields();
1535 foreach ($parent_fields as $field) {
1536 if ($fc->isRequired($field)) {
1537 $fc->setNotRequired($field);
1538 }
1539 }
1540 }
1541
1542 // flagging required fields invisible to members
1543 if ($this->login->isAdmin() || $this->login->isStaff()) {
1544 $fc->setNotRequired('activite_adh');
1545 $fc->setNotRequired('id_statut');
1546 }
1547
1548 $form_elements = $fc->getFormElements(
1549 $this->login,
1550 $member->id == '',
1551 isset($args['self'])
1552 );
1553 $fieldsets = $form_elements['fieldsets'];
1554 $required = array();
1555 $disabled = array();
1556
1557 foreach ($fieldsets as $category) {
1558 foreach ($category->elements as $field) {
1559 if ($field->required == true) {
1560 $required[$field->field_id] = true;
1561 }
1562 if ($field->disabled == true) {
1563 $disabled[$field->field_id] = true;
1564 } elseif (!isset($post[$field->field_id])) {
1565 switch ($field->field_id) {
1566 //unchecked booleans are not sent from form
1567 case 'bool_admin_adh':
1568 case 'bool_exempt_adh':
1569 case 'bool_display_info':
1570 $post[$field->field_id] = 0;
1571 break;
1572 }
1573 }
1574 }
1575 }
1576
1577 $real_requireds = array_diff(array_keys($required), array_keys($disabled));
1578
1579 // Validation
1580 $redirect_url = $this->router->pathFor('member', ['id' => $member->id]);
1581 if (isset($post[array_shift($real_requireds)])) {
1582 // regular fields
1583 $valid = $member->check($post, $required, $disabled);
1584 if ($valid !== true) {
1585 $error_detected = array_merge($error_detected, $valid);
1586 }
1587
1588 if (count($error_detected) == 0) {
1589 //all goes well, we can proceed
1590
1591 $new = false;
1592 if ($member->id == '') {
1593 $new = true;
1594 }
1595 $store = $member->store();
1596 if ($store === true) {
1597 //member has been stored :)
1598 if ($new) {
1599 if (isset($args['self'])) {
1600 $success_detected[] = _T("Your account has been created!");
1601 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1602 && $member->getEmail() != ''
1603 ) {
1604 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1605 }
1606 } else {
1607 $success_detected[] = _T("New member has been successfully added.");
1608 }
1609 //Send email to admin if preference checked
1610 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1611 && $this->preferences->pref_bool_mailadh
1612 ) {
1613 $texts = new Texts(
1614 $this->preferences,
1615 $this->router,
1616 array(
1617 'name_adh' => custom_html_entity_decode(
1618 $member->sname
1619 ),
1620 'firstname_adh' => custom_html_entity_decode(
1621 $member->surname
1622 ),
1623 'lastname_adh' => custom_html_entity_decode(
1624 $member->name
1625 ),
1626 'mail_adh' => custom_html_entity_decode(
1627 $member->email
1628 ),
1629 'login_adh' => custom_html_entity_decode(
1630 $member->login
1631 )
1632 )
1633 );
1634 $mtxt = $texts->getTexts(
1635 (isset($args['self']) ? 'newselfadh' : 'newadh'),
1636 $this->preferences->pref_lang
1637 );
1638
1639 $mail = new GaletteMail($this->preferences);
1640 $mail->setSubject($texts->getSubject());
1641 $recipients = [];
1642 foreach ($this->preferences->vpref_email_newadh as $pref_email) {
1643 $recipients[$pref_email] = $pref_email;
1644 }
1645 $mail->setRecipients($recipients);
1646 $mail->setMessage($texts->getBody());
1647 $sent = $mail->send();
1648
1649 if ($sent == GaletteMail::MAIL_SENT) {
1650 $this->history->add(
1651 str_replace(
1652 '%s',
1653 $member->sname . ' (' . $member->email . ')',
1654 _T("New account email sent to admin for '%s'.")
1655 )
1656 );
1657 } else {
1658 $str = str_replace(
1659 '%s',
1660 $member->sname . ' (' . $member->email . ')',
1661 _T("A problem happened while sending email to admin for account '%s'.")
1662 );
1663 $this->history->add($str);
1664 $warning_detected[] = $str;
1665 }
1666 unset($texts);
1667 }
1668 } else {
1669 $success_detected[] = _T("Member account has been modified.");
1670 }
1671
1672 // send email to member
1673 if (isset($args['self']) || isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1674 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) {
1675 if ($member->getEmail() == '' && !isset($args['self'])) {
1676 $error_detected[] = _T("- You can't send a confirmation by email if the member hasn't got an address!");
1677 } else {
1678 $mreplaces = [
1679 'name_adh' => custom_html_entity_decode(
1680 $member->sname
1681 ),
1682 'firstname_adh' => custom_html_entity_decode(
1683 $member->surname
1684 ),
1685 'lastname_adh' => custom_html_entity_decode(
1686 $member->name
1687 ),
1688 'mail_adh' => custom_html_entity_decode(
1689 $member->getEmail()
1690 ),
1691 'login_adh' => custom_html_entity_decode(
1692 $member->login
1693 )
1694 ];
1695 if ($new) {
1696 $password = new Password($this->zdb);
1697 $res = $password->generateNewPassword($member->id);
1698 if ($res == true) {
1699 $link_validity = new DateTime();
1700 $link_validity->add(new DateInterval('PT24H'));
1701 $mreplaces['change_pass_uri'] = $this->preferences->getURL() .
1702 $this->router->pathFor(
1703 'password-recovery',
1704 ['hash' => base64_encode($password->getHash())]
1705 );
1706 $mreplaces['link_validity'] = $link_validity->format(_T("Y-m-d H:i:s"));
1707 } else {
1708 $str = str_replace(
1709 '%s',
1710 $login_adh,
1711 _T("An error occurred storing temporary password for %s. Please inform an admin.")
1712 );
1713 $this->history->add($str);
1714 $this->flash->addMessage(
1715 'error_detected',
1716 $str
1717 );
1718 }
1719 }
1720
1721 //send email to member
1722 // Get email text in database
1723 $texts = new Texts(
1724 $this->preferences,
1725 $this->router,
1726 $mreplaces
1727 );
1728 $mlang = $this->preferences->pref_lang;
1729 if (isset($post['pref_lang'])) {
1730 $mlang = $post['pref_lang'];
1731 }
1732 $mtxt = $texts->getTexts(
1733 (($new) ? 'sub' : 'accountedited'),
1734 $mlang
1735 );
1736
1737 $mail = new GaletteMail($this->preferences);
1738 $mail->setSubject($texts->getSubject());
1739 $mail->setRecipients(
1740 array(
1741 $member->getEmail() => $member->sname
1742 )
1743 );
1744 $mail->setMessage($texts->getBody());
1745 $sent = $mail->send();
1746
1747 if ($sent == GaletteMail::MAIL_SENT) {
1748 $msg = str_replace(
1749 '%s',
1750 $member->sname . ' (' . $member->getEmail() . ')',
1751 ($new) ?
1752 _T("New account email sent to '%s'.") :
1753 _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 }