]> git.agnieray.net Git - galette.git/blob - galette/includes/routes/members.routes.php
a3d4ed63ef9b0b40e29511978b7d517585297b2f
[galette.git] / galette / includes / routes / members.routes.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Members related routes
7 *
8 * PHP version 5
9 *
10 * Copyright © 2014-2018 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 Routes
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2014-2018 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 0.8.2dev 2014-11-27
36 */
37
38 use Analog\Analog;
39 use Galette\Entity\DynamicFieldsHandle;
40 use Galette\Core\Password;
41 use Galette\Core\PasswordImage;
42 use Galette\Core\Mailing;
43 use Galette\Core\GaletteMail;
44 use Galette\Repository\Members;
45 use Galette\Filters\MembersList;
46 use Galette\Filters\SavedSearchesList;
47 use Galette\Filters\AdvancedMembersList;
48 use Galette\Entity\FieldsConfig;
49 use Galette\Entity\Contribution;
50 use Galette\Repository\Groups;
51 use Galette\Repository\Reminders;
52 use Galette\Entity\Adherent;
53 use Galette\IO\PdfMembersCards;
54 use Galette\IO\PdfMembersLabels;
55 use Galette\IO\Csv;
56 use Galette\IO\CsvOut;
57 use Galette\Entity\Status;
58 use Galette\Repository\Titles;
59 use Galette\Entity\Texts;
60 use Galette\IO\Pdf;
61 use Galette\Core\MailingHistory;
62 use Galette\Entity\Group;
63 use Galette\IO\File;
64 use Galette\Core\Authentication;
65 use Galette\Repository\PaymentTypes;
66 use Galette\Repository\SavedSearches;
67
68 //self subscription
69 $app->get(
70 '/subscribe',
71 function ($request, $response) {
72 if (!$this->preferences->pref_bool_selfsubscribe || $this->login->isLogged()) {
73 return $response
74 ->withStatus(301)
75 ->withHeader('Location', $this->router->pathFor('slash'));
76 }
77
78 if ($this->session->member !== null) {
79 $member = $this->session->member;
80 $this->session->member = null;
81 } else {
82 $deps = [
83 'dynamics' => true
84 ];
85 $member = new Adherent($this->zdb, null, $deps);
86 }
87
88 //mark as self membership
89 $member->setSelfMembership();
90
91 // flagging required fields
92 $fc = $this->fields_config;
93 $form_elements = $fc->getFormElements($this->login, true, true);
94
95 //image to defeat mass filling forms
96 $spam = new PasswordImage();
97 $spam_pass = $spam->newImage();
98 $spam_img = $spam->getImage();
99
100 // members
101 $members = [];
102 $m = new Members();
103 $required_fields = array(
104 'id_adh',
105 'nom_adh',
106 'prenom_adh'
107 );
108 $list_members = $m->getList(false, $required_fields, true);
109
110 if (count($list_members) > 0) {
111 foreach ($list_members as $lmember) {
112 $pk = Adherent::PK;
113 $sname = mb_strtoupper($lmember->nom_adh, 'UTF-8') .
114 ' ' . ucwords(mb_strtolower($lmember->prenom_adh, 'UTF-8')) .
115 ' (' . $lmember->id_adh . ')';
116 $members[$lmember->$pk] = $sname;
117 }
118 }
119
120 $params['members'] = [
121 'filters' => $m->getFilters(),
122 'count' => $m->getCount()
123 ];
124
125 //check if current attached member is part of the list
126 if ($member->hasParent()) {
127 if (!isset($members[$member->parent->id])) {
128 $members =
129 [$member->parent->id => $member->parent->getSName()] +
130 $members
131 ;
132 }
133 }
134
135 if (count($members)) {
136 $params['members']['list'] = $members;
137 }
138
139 // display page
140 $this->view->render(
141 $response,
142 'member.tpl',
143 array(
144 'page_title' => _T("Subscription"),
145 'parent_tpl' => 'public_page.tpl',
146 'member' => $member,
147 'self_adh' => true,
148 'require_calendar' => true,
149 'autocomplete' => true,
150 // pseudo random int
151 'time' => time(),
152 'titles_list' => Titles::getList($this->zdb),
153 //self_adh specific
154 'spam_pass' => $spam_pass,
155 'spam_img' => $spam_img,
156 'fieldsets' => $form_elements['fieldsets'],
157 'hidden_elements' => $form_elements['hiddens']
158 ) + $params
159 );
160 return $response;
161 }
162 )->setName('subscribe');
163
164 //members list CSV export
165 $app->get(
166 '/members/export/csv',
167 function ($request, $response) {
168 $csv = new CsvOut();
169
170 if (isset($this->session->filter_members)) {
171 //CAUTION: this one may be simple or advanced, display must change
172 $filters = $this->session->filter_members;
173 } else {
174 $filters = new MembersList();
175 }
176
177 $export_fields = null;
178 if (file_exists(GALETTE_CONFIG_PATH . 'local_export_fields.inc.php')) {
179 include_once GALETTE_CONFIG_PATH . 'local_export_fields.inc.php';
180 $export_fields = $fields;
181 }
182
183 // fields visibility
184 $fc = $this->fields_config;
185 $visibles = $fc->getVisibilities();
186 //hack for id_adh and parent_id
187 $hacks = ['id_adh', 'parent_id'];
188 foreach ($hacks as $hack) {
189 if ($visibles[$hack] == FieldsConfig::NOBODY) {
190 $visibles[$hack] = FieldsConfig::MANAGER;
191 }
192 }
193 $access_level = $this->login->getAccessLevel();
194 $fields = array();
195 $labels = array();
196 foreach ($this->members_fields as $k => $f) {
197 // skip fields blacklisted for export
198 if ($k === 'mdp_adh' ||
199 ($export_fields !== null &&
200 (is_array($export_fields) && !in_array($k, $export_fields)))
201 ) {
202 continue;
203 }
204
205 // skip fields according to access control
206 if ($visibles[$k] == FieldsConfig::NOBODY ||
207 ($visibles[$k] == FieldsConfig::ADMIN &&
208 $access_level < Authentication::ACCESS_ADMIN) ||
209 ($visibles[$k] == FieldsConfig::STAFF &&
210 $access_level < Authentication::ACCESS_STAFF) ||
211 ($visibles[$k] == FieldsConfig::MANAGER &&
212 $access_level < Authentication::ACCESS_MANAGER)
213 ) {
214 continue;
215 }
216
217 $fields[] = $k;
218 $labels[] = $f['label'];
219 }
220
221 $members = new Members($filters);
222 $members_list = $members->getArrayList(
223 $filters->selected,
224 null,
225 false,
226 false,
227 $fields,
228 true
229 );
230
231 $s = new Status($this->zdb);
232 $statuses = $s->getList();
233
234 $t = new Titles();
235 $titles = $t->getList($this->zdb);
236
237 foreach ($members_list as &$member) {
238 if (isset($member->id_statut)) {
239 //add textual status
240 $member->id_statut = $statuses[$member->id_statut];
241 }
242
243 if (isset($member->titre_adh)) {
244 //add textuel title
245 $member->titre_adh = $titles[$member->titre_adh]->short;
246 }
247
248 //handle dates
249 if (isset($member->date_crea_adh)) {
250 if ($member->date_crea_adh != ''
251 && $member->date_crea_adh != '1901-01-01'
252 ) {
253 $dcrea = new DateTime($member->date_crea_adh);
254 $member->date_crea_adh = $dcrea->format(__("Y-m-d"));
255 } else {
256 $member->date_crea_adh = '';
257 }
258 }
259
260 if (isset($member->date_modif_adh)) {
261 if ($member->date_modif_adh != ''
262 && $member->date_modif_adh != '1901-01-01'
263 ) {
264 $dmodif = new DateTime($member->date_modif_adh);
265 $member->date_modif_adh = $dmodif->format(__("Y-m-d"));
266 } else {
267 $member->date_modif_adh = '';
268 }
269 }
270
271 if (isset($member->date_echeance)) {
272 if ($member->date_echeance != ''
273 && $member->date_echeance != '1901-01-01'
274 ) {
275 $dech = new DateTime($member->date_echeance);
276 $member->date_echeance = $dech->format(__("Y-m-d"));
277 } else {
278 $member->date_echeance = '';
279 }
280 }
281
282 if (isset($member->ddn_adh)) {
283 if ($member->ddn_adh != ''
284 && $member->ddn_adh != '1901-01-01'
285 ) {
286 $ddn = new DateTime($member->ddn_adh);
287 $member->ddn_adh = $ddn->format(__("Y-m-d"));
288 } else {
289 $member->ddn_adh = '';
290 }
291 }
292
293 if (isset($member->sexe_adh)) {
294 //handle gender
295 switch ($member->sexe_adh) {
296 case Adherent::MAN:
297 $member->sexe_adh = _T("Man");
298 break;
299 case Adherent::WOMAN:
300 $member->sexe_adh = _T("Woman");
301 break;
302 case Adherent::NC:
303 $member->sexe_adh = _T("Unspecified");
304 break;
305 }
306 }
307
308 //handle booleans
309 if (isset($member->activite_adh)) {
310 $member->activite_adh
311 = ($member->activite_adh) ? _T("Yes") : _T("No");
312 }
313 if (isset($member->bool_admin_adh)) {
314 $member->bool_admin_adh
315 = ($member->bool_admin_adh) ? _T("Yes") : _T("No");
316 }
317 if (isset($member->bool_exempt_adh)) {
318 $member->bool_exempt_adh
319 = ($member->bool_exempt_adh) ? _T("Yes") : _T("No");
320 }
321 if (isset($member->bool_display_info)) {
322 $member->bool_display_info
323 = ($member->bool_display_info) ? _T("Yes") : _T("No");
324 }
325 }
326 $filename = 'filtered_memberslist.csv';
327 $filepath = CsvOut::DEFAULT_DIRECTORY . $filename;
328 $fp = fopen($filepath, 'w');
329 if ($fp) {
330 $res = $csv->export(
331 $members_list,
332 Csv::DEFAULT_SEPARATOR,
333 Csv::DEFAULT_QUOTE,
334 $labels,
335 $fp
336 );
337 fclose($fp);
338 $written[] = array(
339 'name' => $filename,
340 'file' => $filepath
341 );
342 }
343
344 $filepath = CsvOut::DEFAULT_DIRECTORY . $filename;
345 if (file_exists($filepath)) {
346 $response = $this->response->withHeader('Content-Description', 'File Transfer')
347 ->withHeader('Content-Type', 'text/csv')
348 ->withHeader('Content-Disposition', 'attachment;filename="' . $filename . '"')
349 ->withHeader('Pragma', 'no-cache')
350 ->withHeader('Content-Transfer-Encoding', 'binary')
351 ->withHeader('Expires', '0')
352 ->withHeader('Cache-Control', 'must-revalidate')
353 ->withHeader('Pragma', 'public');
354
355 $stream = fopen('php://memory', 'r+');
356 fwrite($stream, file_get_contents($filepath));
357 rewind($stream);
358
359 return $response->withBody(new \Slim\Http\Stream($stream));
360 } else {
361 Analog::log(
362 'A request has been made to get an exported file named `' .
363 $filename .'` that does not exists.',
364 Analog::WARNING
365 );
366 $notFound = $this->notFoundHandler;
367 return $notFound($request, $response);
368 }
369 return $response;
370 }
371 )->setName('csv-memberslist')->add($authenticate);
372
373 //members list
374 $app->get(
375 '/members[/{option:page|order}/{value:\d+}]',
376 function ($request, $response, $args = []) {
377 $option = null;
378 if (isset($args['option'])) {
379 $option = $args['option'];
380 }
381 $value = null;
382 if (isset($args['value'])) {
383 $value = $args['value'];
384 }
385
386 if (isset($this->session->filter_members)) {
387 $filters = $this->session->filter_members;
388 } else {
389 $filters = new MembersList();
390 }
391
392 if ($option !== null) {
393 switch ($option) {
394 case 'page':
395 $filters->current_page = (int)$value;
396 break;
397 case 'order':
398 $filters->orderby = $value;
399 break;
400 }
401 }
402
403 $members = new Members($filters);
404
405 $members_list = array();
406 if ($this->login->isAdmin() || $this->login->isStaff()) {
407 $members_list = $members->getMembersList(true);
408 } else {
409 $members_list = $members->getManagedMembersList(true);
410 }
411
412 $groups = new Groups($this->zdb, $this->login);
413 $groups_list = $groups->getList();
414
415 //assign pagination variables to the template and add pagination links
416 $filters->setSmartyPagination($this->router, $this->view->getSmarty(), false);
417 $filters->setViewCommonsFilters($this->preferences, $this->view->getSmarty());
418
419 $this->session->filter_members = $filters;
420
421 // display page
422 $this->view->render(
423 $response,
424 'gestion_adherents.tpl',
425 array(
426 'page_title' => _T("Members management"),
427 'require_dialog' => true,
428 'require_calendar' => true,
429 'require_mass' => true,
430 'members' => $members_list,
431 'filter_groups_options' => $groups_list,
432 'nb_members' => $members->getCount(),
433 'filters' => $filters,
434 'adv_filters' => $filters instanceof AdvancedMembersList
435 )
436 );
437 return $response;
438 }
439 )->setName(
440 'members'
441 )->add($authenticate);
442
443 //members list filtering
444 $app->post(
445 '/members/filter',
446 function ($request, $response) {
447 $post = $request->getParsedBody();
448 if (isset($this->session->filter_members)) {
449 //CAUTION: this one may be simple or advanced, display must change
450 $filters = $this->session->filter_members;
451 } else {
452 $filters = new MembersList();
453 }
454
455 //reintialize filters
456 if (isset($post['clear_filter'])) {
457 $filters = new MembersList();
458 } elseif (isset($post['clear_adv_filter'])) {
459 $this->session->filter_members = null;
460 unset($this->session->filter_members);
461
462 return $response
463 ->withStatus(301)
464 ->withHeader('Location', $this->router->pathFor('advanced-search'));
465 } elseif (isset($post['adv_criteria'])) {
466 return $response
467 ->withStatus(301)
468 ->withHeader('Location', $this->router->pathFor('advanced-search'));
469 } else {
470 //string to filter
471 if (isset($post['filter_str'])) { //filter search string
472 $filters->filter_str = stripslashes(
473 htmlspecialchars($post['filter_str'], ENT_QUOTES)
474 );
475 }
476 //field to filter
477 if (isset($post['field_filter'])) {
478 if (is_numeric($post['field_filter'])) {
479 $filters->field_filter = $post['field_filter'];
480 }
481 }
482 //membership to filter
483 if (isset($post['membership_filter'])) {
484 if (is_numeric($post['membership_filter'])) {
485 $filters->membership_filter
486 = $post['membership_filter'];
487 }
488 }
489 //account status to filter
490 if (isset($post['filter_account'])) {
491 if (is_numeric($post['filter_account'])) {
492 $filters->filter_account = $post['filter_account'];
493 }
494 }
495 //email filter
496 if (isset($post['email_filter'])) {
497 $filters->email_filter = (int)$post['email_filter'];
498 }
499 //group filter
500 if (isset($post['group_filter'])
501 && $post['group_filter'] > 0
502 ) {
503 $filters->group_filter = (int)$post['group_filter'];
504 }
505 //number of rows to show
506 if (isset($post['nbshow'])) {
507 $filters->show = $post['nbshow'];
508 }
509
510 if (isset($post['advanced_filtering'])) {
511 if (!$filters instanceof AdvancedMembersList) {
512 $filters = new AdvancedMembersList($filters);
513 }
514 //Advanced filters
515 $filters->reinit();
516 unset($post['advanced_filtering']);
517 $freed = false;
518 foreach ($post as $k => $v) {
519 if (strpos($k, 'free_', 0) === 0) {
520 if (!$freed) {
521 $i = 0;
522 foreach ($post['free_field'] as $f) {
523 if (trim($f) !== ''
524 && trim($post['free_text'][$i]) !== ''
525 ) {
526 $fs_search = $post['free_text'][$i];
527 $log_op
528 = (int)$post['free_logical_operator'][$i];
529 $qry_op
530 = (int)$post['free_query_operator'][$i];
531 $type = (int)$post['free_type'][$i];
532 $fs = array(
533 'idx' => $i,
534 'field' => $f,
535 'type' => $type,
536 'search' => $fs_search,
537 'log_op' => $log_op,
538 'qry_op' => $qry_op
539 );
540 $filters->free_search = $fs;
541 }
542 $i++;
543 }
544 $freed = true;
545 }
546 } else {
547 switch ($k) {
548 case 'contrib_min_amount':
549 case 'contrib_max_amount':
550 if (trim($v) !== '') {
551 $v = (float)$v;
552 } else {
553 $v = null;
554 }
555 break;
556 }
557 $filters->$k = $v;
558 }
559 }
560 }
561 }
562
563 if (isset($post['savesearch'])) {
564 return $response
565 ->withStatus(301)
566 ->withHeader(
567 'Location',
568 $this->router->pathFor(
569 'saveSearch',
570 $post
571 )
572 );
573 }
574
575 $this->session->filter_members = $filters;
576
577 return $response
578 ->withStatus(301)
579 ->withHeader('Location', $this->router->pathFor('members'));
580 }
581 )->setName('filter-memberslist')->add($authenticate);
582
583 //members self card
584 $app->get(
585 '/member/me',
586 function ($request, $response) {
587 if ($this->login->isSuperAdmin()) {
588 return $response
589 ->withStatus(301)
590 ->withHeader('Location', $this->router->pathFor('slash'));
591 }
592 $deps = array(
593 'picture' => true,
594 'groups' => true,
595 'dues' => true,
596 'parent' => true,
597 'children' => true,
598 'dynamics' => true
599 );
600
601 $member = new Adherent($this->zdb, $this->login->login, $deps);
602 $id = $member->id;
603
604 $fc = $this->fields_config;
605 $display_elements = $fc->getDisplayElements($this->login);
606
607 // display page
608 $this->view->render(
609 $response,
610 'voir_adherent.tpl',
611 array(
612 'page_title' => _T("Member Profile"),
613 'require_dialog' => true,
614 'member' => $member,
615 'pref_lang' => $this->i18n->getNameFromId($member->language),
616 'pref_card_self' => $this->preferences->pref_card_self,
617 'groups' => Groups::getSimpleList(),
618 'time' => time(),
619 'display_elements' => $display_elements
620 )
621 );
622 }
623 )->setName('me')->add($authenticate);
624
625 //members card
626 $app->get(
627 '/member/{id:\d+}',
628 function ($request, $response, $args) {
629 $id = $args['id'];
630
631 $deps = array(
632 'picture' => true,
633 'groups' => true,
634 'dues' => true,
635 'parent' => true,
636 'children' => true,
637 'dynamics' => true
638 );
639 $member = new Adherent($this->zdb, (int)$id, $deps);
640
641 if ($this->login->id != $id && !$this->login->isAdmin() && !$this->login->isStaff()) {
642 //check if requested member is part of managed groups
643 $groups = $member->groups;
644 $is_managed = false;
645 foreach ($groups as $g) {
646 if ($this->login->isGroupManager($g->getId())) {
647 $is_managed = true;
648 break;
649 }
650 }
651 if ($is_managed !== true) {
652 //requested member is not part of managed groups,
653 //fall back to logged in member
654 Analog::log(
655 'Trying to display member #' . $id . ' without appropriate ACLs',
656 Analog::WARNING
657 );
658
659 return $response
660 ->withStatus(403)
661 ->withHeader(
662 'Location',
663 $this->router->pathFor(
664 'member',
665 ['id' => $this->login->id]
666 )
667 );
668 }
669 }
670
671 if ($member->id == null) {
672 //member does not exists!
673 $this->flash->addMessage(
674 'error_detected',
675 str_replace('%id', $args['id'], _T("No member #%id."))
676 );
677
678 return $response
679 ->withStatus(404)
680 ->withHeader(
681 'Location',
682 $this->router->pathFor('slash')
683 );
684 }
685
686 // flagging fields visibility
687 $fc = $this->fields_config;
688 $display_elements = $fc->getDisplayElements($this->login);
689
690 // display page
691 $this->view->render(
692 $response,
693 'voir_adherent.tpl',
694 array(
695 'page_title' => _T("Member Profile"),
696 'require_dialog' => true,
697 'member' => $member,
698 'pref_lang' => $this->i18n->getNameFromId($member->language),
699 'pref_card_self' => $this->preferences->pref_card_self,
700 'groups' => Groups::getSimpleList(),
701 'time' => time(),
702 'display_elements' => $display_elements
703 )
704 );
705 return $response;
706 }
707 )->setName('member')->add($authenticate)->add($navMiddleware);
708
709 $app->get(
710 '/member/{action:edit|add}[/{id:\d+}]',
711 function ($request, $response, $args) {
712 $action = $args['action'];
713 $id = null;
714 if (isset($args['id'])) {
715 $id = $args['id'];
716 }
717
718 if ($action === 'edit' && $id === null) {
719 throw new \RuntimeException(
720 _T("Member ID cannot ben null calling edit route!")
721 );
722 } elseif ($action === 'add' && $id !== null) {
723 return $response
724 ->withStatus(301)
725 ->withHeader('Location', $this->router->pathFor('editmember', ['action' => 'add']));
726 }
727 $deps = array(
728 'picture' => true,
729 'groups' => true,
730 'dues' => true,
731 'parent' => true,
732 'children' => true,
733 'dynamics' => true
734 );
735 $route_params = [];
736
737 if ($this->session->member !== null) {
738 $member = $this->session->member;
739 $this->session->member = null;
740 } else {
741 $member = new Adherent($this->zdb, null, $deps);
742 }
743
744 if ($this->login->isAdmin() || $this->login->isStaff() || $this->login->isGroupManager()) {
745 if ($id !== null) {
746 if ($member->id != $id) {
747 $member->load($id);
748 }
749 if (!$this->login->isAdmin() && !$this->login->isStaff() && $this->login->isGroupManager()) {
750 //check if current logged in user can manage loaded member
751 $groups = $member->groups;
752 $can_manage = false;
753 foreach ($groups as $group) {
754 if ($this->login->isGroupManager($group->getId())) {
755 $can_manage = true;
756 break;
757 }
758 }
759 if ($can_manage !== true) {
760 Analog::log(
761 'Logged in member ' . $this->login->login .
762 ' has tried to load member #' . $member->id .
763 ' but do not manage any groups he belongs to.',
764 Analog::WARNING
765 );
766 $member->load($this->login->id);
767 }
768 }
769 }
770 } else {
771 if ($member->id != $id) {
772 $member->load($this->login->id);
773 }
774 }
775
776 // flagging required fields
777 $fc = $this->fields_config;
778
779 // password required if we create a new member
780 if ($member->id != '') {
781 $fc->setNotRequired('mdp_adh');
782 }
783
784 //handle requirements for parent fields
785 $parent_fields = $member->getParentFields();
786 foreach ($parent_fields as $key => $field) {
787 if ($fc->isRequired($field) && $member->hasParent()) {
788 $fc->setNotRequired($field);
789 } elseif (!$fc->isRequired($field)) {
790 unset($parent_fields[$key]);
791 }
792 }
793 $route_params['parent_fields'] = $parent_fields;
794
795 // flagging required fields invisible to members
796 if ($this->login->isAdmin() || $this->login->isStaff()) {
797 $fc->setNotRequired('activite_adh');
798 $fc->setNotRequired('id_statut');
799 }
800
801 // template variable declaration
802 $title = _T("Member Profile");
803 if ($member->id != '') {
804 $title .= ' (' . _T("modification") . ')';
805 } else {
806 $title .= ' (' . _T("creation") . ')';
807 }
808
809 //Status
810 $statuts = new Status($this->zdb);
811
812 //Groups
813 $groups = new Groups($this->zdb, $this->login);
814 $groups_list = $groups->getSimpleList(true);
815
816 $form_elements = $fc->getFormElements(
817 $this->login,
818 $member->id == ''
819 );
820
821 // members
822 $members = [];
823 $m = new Members();
824 $required_fields = array(
825 'id_adh',
826 'nom_adh',
827 'prenom_adh'
828 );
829 $list_members = $m->getList(false, $required_fields, true);
830
831 if (count($list_members) > 0) {
832 foreach ($list_members as $lmember) {
833 $pk = Adherent::PK;
834 $sname = mb_strtoupper($lmember->nom_adh, 'UTF-8') .
835 ' ' . ucwords(mb_strtolower($lmember->prenom_adh, 'UTF-8')) .
836 ' (' . $lmember->id_adh . ')';
837 $members[$lmember->$pk] = $sname;
838 }
839 }
840
841 $route_params['members'] = [
842 'filters' => $m->getFilters(),
843 'count' => $m->getCount()
844 ];
845
846 //check if current attached member is part of the list
847 if ($member->hasParent()) {
848 if (!isset($members[$member->parent->id])) {
849 $members =
850 [$member->parent->id => $member->parent->getSName()] +
851 $members
852 ;
853 }
854 }
855
856 if (count($members)) {
857 $route_params['members']['list'] = $members;
858 }
859
860 // display page
861 $this->view->render(
862 $response,
863 'member.tpl',
864 array_merge(
865 $route_params,
866 array(
867 'parent_tpl' => 'page.tpl',
868 'require_dialog' => true,
869 'autocomplete' => true,
870 'page_title' => $title,
871 'member' => $member,
872 'self_adh' => false,
873 'require_calendar' => true,
874 // pseudo random int
875 'time' => time(),
876 'titles_list' => Titles::getList($this->zdb),
877 'statuts' => $statuts->getList(),
878 'groups' => $groups_list,
879 'fieldsets' => $form_elements['fieldsets'],
880 'hidden_elements' => $form_elements['hiddens']
881 )
882 )
883 );
884 return $response;
885 }
886 )->setName(
887 'editmember'
888 )->add($authenticate)->add($navMiddleware);
889
890 $app->post(
891 '/member/store[/{self:subscribe}]',
892 function ($request, $response, $args) {
893 if (!$this->preferences->pref_bool_selfsubscribe && !$this->login->isLogged()) {
894 return $response
895 ->withStatus(301)
896 ->withHeader('Location', $this->router->pathFor('slash'));
897 }
898
899 $post = $request->getParsedBody();
900 $deps = array(
901 'picture' => true,
902 'groups' => true,
903 'dues' => true,
904 'parent' => true,
905 'children' => true,
906 'dynamics' => true
907 );
908 $member = new Adherent($this->zdb, null, $deps);
909 $member->setDependencies(
910 $this->preferences,
911 $this->members_fields,
912 $this->history
913 );
914 if (isset($args['self'])) {
915 //mark as self membership
916 $member->setSelfMembership();
917 }
918
919 $success_detected = [];
920 $warning_detected = [];
921 $error_detected = [];
922
923 // new or edit
924 $adherent['id_adh'] = get_numeric_form_value('id_adh', '');
925
926 if ($this->login->isAdmin() || $this->login->isStaff() || $this->login->isGroupManager()) {
927 if ($adherent['id_adh']) {
928 $member->load($adherent['id_adh']);
929 if (!$this->login->isAdmin() && !$this->login->isStaff() && $this->login->isGroupManager()) {
930 //check if current logged in user can manage loaded member
931 $groups = $member->groups;
932 $can_manage = false;
933 foreach ($groups as $group) {
934 if ($this->login->isGroupManager($group->getId())) {
935 $can_manage = true;
936 break;
937 }
938 }
939 if ($can_manage !== true) {
940 Analog::log(
941 'Logged in member ' . $this->login->login .
942 ' has tried to load member #' . $member->id .
943 ' but do not manage any groups he belongs to.',
944 Analog::WARNING
945 );
946 $member->load($this->login->id);
947 }
948 }
949 }
950 } else {
951 $member->load($this->login->id);
952 $adherent['id_adh'] = $this->login->id;
953 }
954
955 // flagging required fields
956 $fc = $this->fields_config;
957
958 // password required if we create a new member
959 if ($member->id != '') {
960 $fc->setNotRequired('mdp_adh');
961 }
962
963 if ($member->hasParent() && !isset($post['detach_parent'])
964 || isset($post['parent_id']) && !empty($post['parent_id'])
965 ) {
966 $parent_fields = $member->getParentFields();
967 foreach ($parent_fields as $field) {
968 if ($fc->isRequired($field)) {
969 $fc->setNotRequired($field);
970 }
971 }
972 }
973
974 // flagging required fields invisible to members
975 if ($this->login->isAdmin() || $this->login->isStaff()) {
976 $fc->setNotRequired('activite_adh');
977 $fc->setNotRequired('id_statut');
978 }
979
980 $form_elements = $fc->getFormElements(
981 $this->login,
982 $member->id == '',
983 isset($args['self'])
984 );
985 $fieldsets = $form_elements['fieldsets'];
986 $required = array();
987 $disabled = array();
988
989 foreach ($fieldsets as $category) {
990 foreach ($category->elements as $field) {
991 if ($field->required == true) {
992 $required[$field->field_id] = true;
993 }
994 if ($field->disabled == true) {
995 $disabled[$field->field_id] = true;
996 } elseif (!isset($post[$field->field_id])) {
997 switch ($field->field_id) {
998 //unchecked booleans are not sent from form
999 case 'bool_admin_adh':
1000 case 'bool_exempt_adh':
1001 case 'bool_display_info':
1002 $post[$field->field_id] = 0;
1003 break;
1004 }
1005 }
1006 }
1007 }
1008
1009 $real_requireds = array_diff(array_keys($required), array_keys($disabled));
1010
1011 // Validation
1012 if (isset($post[array_shift($real_requireds)])) {
1013 // regular fields
1014 $valid = $member->check($post, $required, $disabled);
1015 if ($valid !== true) {
1016 $error_detected = array_merge($error_detected, $valid);
1017 }
1018
1019 if (count($error_detected) == 0) {
1020 //all goes well, we can proceed
1021
1022 $new = false;
1023 if ($member->id == '') {
1024 $new = true;
1025 }
1026 $store = $member->store();
1027 if ($store === true) {
1028 //member has been stored :)
1029 if ($new) {
1030 if (isset($args['self'])) {
1031 $success_detected[] = _T("Your account has been created!");
1032 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1033 && $member->getEmail() != ''
1034 ) {
1035 $success_detected[] = _T("An email has been sent to you, check your inbox.");
1036 }
1037 } else {
1038 $success_detected[] = _T("New member has been successfully added.");
1039 }
1040 //Send email to admin if preference checked
1041 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1042 && $this->preferences->pref_bool_mailadh
1043 ) {
1044 $texts = new Texts(
1045 $this->texts_fields,
1046 $this->preferences,
1047 $this->router,
1048 array(
1049 'name_adh' => custom_html_entity_decode(
1050 $member->sname
1051 ),
1052 'firstname_adh' => custom_html_entity_decode(
1053 $member->surname
1054 ),
1055 'lastname_adh' => custom_html_entity_decode(
1056 $member->name
1057 ),
1058 'mail_adh' => custom_html_entity_decode(
1059 $member->email
1060 ),
1061 'login_adh' => custom_html_entity_decode(
1062 $member->login
1063 )
1064 )
1065 );
1066 $mtxt = $texts->getTexts(
1067 (isset($args['self']) ? 'newselfadh' : 'newadh'),
1068 $this->preferences->pref_lang
1069 );
1070
1071 $mail = new GaletteMail($this->preferences);
1072 $mail->setSubject($texts->getSubject());
1073 $recipients = [];
1074 foreach ($this->preferences->vpref_email_newadh as $pref_email) {
1075 $recipients[$pref_email] = $pref_email;
1076 }
1077 $mail->setRecipients($recipients);
1078 $mail->setMessage($texts->getBody());
1079 $sent = $mail->send();
1080
1081 if ($sent == GaletteMail::MAIL_SENT) {
1082 $this->history->add(
1083 str_replace(
1084 '%s',
1085 $member->sname . ' (' . $member->email . ')',
1086 _T("New account mail sent to admin for '%s'.")
1087 )
1088 );
1089 } else {
1090 $str = str_replace(
1091 '%s',
1092 $member->sname . ' (' . $member->email . ')',
1093 _T("A problem happened while sending email to admin for account '%s'.")
1094 );
1095 $this->history->add($str);
1096 $warning_detected[] = $str;
1097 }
1098 unset($texts);
1099 }
1100 } else {
1101 $success_detected[] = _T("Member account has been modified.");
1102 }
1103
1104 // send mail to member
1105 if (isset($args['self']) || isset($post['mail_confirm']) && $post['mail_confirm'] == '1') {
1106 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED) {
1107 if ($member->getEmail() == '' && !isset($args['self'])) {
1108 $error_detected[] = _T("- You can't send a confirmation by email if the member hasn't got an address!");
1109 } else {
1110 $mreplaces = [
1111 'name_adh' => custom_html_entity_decode(
1112 $member->sname
1113 ),
1114 'firstname_adh' => custom_html_entity_decode(
1115 $member->surname
1116 ),
1117 'lastname_adh' => custom_html_entity_decode(
1118 $member->name
1119 ),
1120 'mail_adh' => custom_html_entity_decode(
1121 $member->getEmail()
1122 ),
1123 'login_adh' => custom_html_entity_decode(
1124 $member->login
1125 )
1126 ];
1127 if ($new) {
1128 $password = new Password($this->zdb);
1129 $res = $password->generateNewPassword($member->id);
1130 if ($res == true) {
1131 $link_validity = new DateTime();
1132 $link_validity->add(new DateInterval('PT24H'));
1133 $mreplaces['change_pass_uri'] = $this->preferences->getURL() .
1134 $this->router->pathFor(
1135 'password-recovery',
1136 ['hash' => base64_encode($password->getHash())]
1137 );
1138 $mreplaces['link_validity'] = $link_validity->format(_T("Y-m-d H:i:s"));
1139 } else {
1140 $str = str_replace(
1141 '%s',
1142 $login_adh,
1143 _T("An error occurred storing temporary password for %s. Please inform an admin.")
1144 );
1145 $this->history->add($str);
1146 $this->flash->addMessage(
1147 'error_detected',
1148 $str
1149 );
1150 }
1151 }
1152
1153 //send mail to member
1154 // Get email text in database
1155 $texts = new Texts(
1156 $this->texts_fields,
1157 $this->preferences,
1158 $this->router,
1159 $mreplaces
1160 );
1161 $mlang = $this->preferences->pref_lang;
1162 if (isset($post['pref_lang'])) {
1163 $mlang = $post['pref_lang'];
1164 }
1165 $mtxt = $texts->getTexts(
1166 (($new) ? 'sub' : 'accountedited'),
1167 $mlang
1168 );
1169
1170 $mail = new GaletteMail($this->preferences);
1171 $mail->setSubject($texts->getSubject());
1172 $mail->setRecipients(
1173 array(
1174 $member->getEmail() => $member->sname
1175 )
1176 );
1177 $mail->setMessage($texts->getBody());
1178 $sent = $mail->send();
1179
1180 if ($sent == GaletteMail::MAIL_SENT) {
1181 $msg = str_replace(
1182 '%s',
1183 $member->sname . ' (' . $member->getEmail() . ')',
1184 ($new) ?
1185 _T("New account mail sent to '%s'.") :
1186 _T("Account modification mail sent to '%s'.")
1187 );
1188 $this->history->add($msg);
1189 $success_detected[] = $msg;
1190 } else {
1191 $str = str_replace(
1192 '%s',
1193 $member->sname . ' (' . $member->getEmail() . ')',
1194 _T("A problem happened while sending account mail to '%s'")
1195 );
1196 $this->history->add($str);
1197 $error_detected[] = $str;
1198 }
1199 }
1200 } elseif ($this->preferences->pref_mail_method == GaletteMail::METHOD_DISABLED) {
1201 //if mail has been disabled in the preferences, we should not be here ;
1202 //we do not throw an error, just a simple warning that will be show later
1203 $msg = _T("You asked Galette to send a confirmation mail to the member, but mail has been disabled in the preferences.");
1204 $warning_detected[] = $msg;
1205 }
1206 }
1207
1208 // send mail to admin
1209 if ($this->preferences->pref_mail_method > GaletteMail::METHOD_DISABLED
1210 && $this->preferences->pref_bool_mailadh
1211 && !$new
1212 && $member->id == $this->login->id
1213 ) {
1214 $mreplaces = [
1215 'name_adh' => custom_html_entity_decode(
1216 $member->sname
1217 ),
1218 'firstname_adh' => custom_html_entity_decode(
1219 $member->surname
1220 ),
1221 'lastname_adh' => custom_html_entity_decode(
1222 $member->name
1223 ),
1224 'mail_adh' => custom_html_entity_decode(
1225 $member->getEmail()
1226 ),
1227 'login_adh' => custom_html_entity_decode(
1228 $member->login
1229 )
1230 ];
1231
1232 //send mail to member
1233 // Get email text in database
1234 $texts = new Texts(
1235 $this->texts_fields,
1236 $this->preferences,
1237 $this->router,
1238 $mreplaces
1239 );
1240 $mlang = $this->preferences->pref_lang;
1241
1242 $mtxt = $texts->getTexts(
1243 'admaccountedited',
1244 $mlang
1245 );
1246
1247 $mail = new GaletteMail($this->preferences);
1248 $mail->setSubject($texts->getSubject());
1249 $recipients = [];
1250 foreach ($this->preferences->vpref_email_newadh as $pref_email) {
1251 $recipients[$pref_email] = $pref_email;
1252 }
1253 $mail->setRecipients($recipients);
1254
1255 $mail->setMessage($texts->getBody());
1256 $sent = $mail->send();
1257
1258 if ($sent == GaletteMail::MAIL_SENT) {
1259 $msg = _T("Account modification mail sent to admin.");
1260 $this->history->add($msg);
1261 $success_detected[] = $msg;
1262 } else {
1263 $str = _T("A problem happened while sending account mail to admin");
1264 $this->history->add($str);
1265 $error_detected[] = $str;
1266 }
1267 }
1268
1269 //store requested groups
1270 $add_groups = null;
1271 $groups_adh = null;
1272 $managed_groups_adh = null;
1273
1274 //add/remove user from groups
1275 if (isset($post['groups_adh'])) {
1276 $groups_adh = $post['groups_adh'];
1277 }
1278 $add_groups = Groups::addMemberToGroups(
1279 $member,
1280 $groups_adh
1281 );
1282
1283 if ($add_groups === false) {
1284 $error_detected[] = _T("An error occurred adding member to its groups.");
1285 }
1286
1287 //add/remove manager from groups
1288 if (isset($post['groups_managed_adh'])) {
1289 $managed_groups_adh = $post['groups_managed_adh'];
1290 }
1291 $add_groups = Groups::addMemberToGroups(
1292 $member,
1293 $managed_groups_adh,
1294 true
1295 );
1296 $member->loadGroups();
1297
1298 if ($add_groups === false) {
1299 $error_detected[] = _T("An error occurred adding member to its groups as manager.");
1300 }
1301 } else {
1302 //something went wrong :'(
1303 $error_detected[] = _T("An error occurred while storing the member.");
1304 }
1305 }
1306
1307 if (count($error_detected) == 0) {
1308 $files_res = $member->handleFiles($_FILES);
1309 if (is_array($files_res)) {
1310 $error_detected = array_merge($error_detected, $files_res);
1311 }
1312
1313 if (isset($post['del_photo'])) {
1314 if (!$member->picture->delete($member->id)) {
1315 $error_detected[] = _T("Delete failed");
1316 $str_adh = $member->id . ' (' . $member->sname . ' ' . ')';
1317 Analog::log(
1318 'Unable to delete picture for member ' . $str_adh,
1319 Analog::ERROR
1320 );
1321 }
1322 }
1323 }
1324
1325 if (count($error_detected) > 0) {
1326 foreach ($error_detected as $error) {
1327 if (strpos($error, '%member_url_') !== false) {
1328 preg_match('/%member_url_(\d+)/', $error, $matches);
1329 $url = $this->router->pathFor('member', ['id' => $matches[1]]);
1330 $error = str_replace(
1331 '%member_url_' . $matches[1],
1332 $url,
1333 $error
1334 );
1335 }
1336 $this->flash->addMessage(
1337 'error_detected',
1338 $error
1339 );
1340 }
1341 }
1342
1343 if (count($warning_detected) > 0) {
1344 foreach ($warning_detected as $warning) {
1345 $this->flash->addMessage(
1346 'warning_detected',
1347 $warning
1348 );
1349 }
1350 }
1351 if (count($success_detected) > 0) {
1352 foreach ($success_detected as $success) {
1353 $this->flash->addMessage(
1354 'success_detected',
1355 $success
1356 );
1357 }
1358 }
1359
1360 if (count($error_detected) == 0) {
1361 $redirect_url = null;
1362 if (isset($args['self'])) {
1363 $redirect_url = $this->router->pathFor('login');
1364 } elseif (isset($post['redirect_on_create'])
1365 && $post['redirect_on_create'] > Adherent::AFTER_ADD_DEFAULT
1366 ) {
1367 switch ($post['redirect_on_create']) {
1368 case Adherent::AFTER_ADD_TRANS:
1369 $redirect_url = $this->router->pathFor('transaction', ['action' => 'add']);
1370 break;
1371 case Adherent::AFTER_ADD_NEW:
1372 $redirect_url = $this->router->pathFor('editmember', ['action' => 'add']);
1373 break;
1374 case Adherent::AFTER_ADD_SHOW:
1375 $redirect_url = $this->router->pathFor('member', ['id' => $member->id]);
1376 break;
1377 case Adherent::AFTER_ADD_LIST:
1378 $redirect_url = $this->router->pathFor('members');
1379 break;
1380 case Adherent::AFTER_ADD_HOME:
1381 $redirect_url = $this->router->pathFor('slash');
1382 break;
1383 }
1384 } elseif (!isset($post['id_adh']) && !$member->isDueFree()) {
1385 $redirect_url = $this->router->pathFor(
1386 'contribution',
1387 [
1388 'type' => 'fee',
1389 'action' => 'add',
1390 ]
1391 ) . '?id_adh=' . $member->id;
1392 } else {
1393 $redirect_url = $this->router->pathFor('member', ['id' => $member->id]);
1394 }
1395
1396 return $response
1397 ->withStatus(301)
1398 ->withHeader('Location', $redirect_url);
1399 } else {
1400 //store entity in session
1401 $this->session->member = $member;
1402
1403 if (isset($args['self'])) {
1404 $redirect_url = $this->router->pathFor('subscribe');
1405 } else {
1406 if ($member->id) {
1407 $rparams = [
1408 'id' => $member->id,
1409 'action' => 'edit'
1410 ];
1411 } else {
1412 $rparams = ['action' => 'add'];
1413 }
1414 $redirect_url = $this->router->pathFor(
1415 'editmember',
1416 $rparams
1417 );
1418 }
1419
1420 return $response
1421 ->withStatus(301)
1422 ->withHeader('Location', $redirect_url);
1423 }
1424 }
1425 }
1426 )->setName('storemembers');
1427
1428 $app->get(
1429 '/member/remove/{id:\d+}',
1430 function ($request, $response, $args) {
1431 $adh = new Adherent($this->zdb, (int)$args['id']);
1432
1433 $data = [
1434 'id' => $args['id'],
1435 'redirect_uri' => $this->router->pathFor('members')
1436 ];
1437
1438 // display page
1439 $this->view->render(
1440 $response,
1441 'confirm_removal.tpl',
1442 array(
1443 'type' => _T("Member"),
1444 'mode' => $request->isXhr() ? 'ajax' : '',
1445 'page_title' => sprintf(
1446 _T('Remove member %1$s'),
1447 $adh->sfullname
1448 ),
1449 'form_url' => $this->router->pathFor('doRemoveMember', ['id' => $adh->id]),
1450 'cancel_uri' => $this->router->pathFor('members'),
1451 'data' => $data
1452 )
1453 );
1454 return $response;
1455 }
1456 )->setName('removeMember')->add($authenticate);
1457
1458 $app->get(
1459 '/members/remove',
1460 function ($request, $response) {
1461 $filters = $this->session->filter_members;
1462
1463 $data = [
1464 'id' => $filters->selected,
1465 'redirect_uri' => $this->router->pathFor('members')
1466 ];
1467
1468 // display page
1469 $this->view->render(
1470 $response,
1471 'confirm_removal.tpl',
1472 array(
1473 'type' => _T("Member"),
1474 'mode' => $request->isXhr() ? 'ajax' : '',
1475 'page_title' => _T('Remove members'),
1476 'message' => str_replace(
1477 '%count',
1478 count($data['id']),
1479 _T('You are about to remove %count members.')
1480 ),
1481 'form_url' => $this->router->pathFor('doRemoveMember'),
1482 'cancel_uri' => $this->router->pathFor('members'),
1483 'data' => $data
1484 )
1485 );
1486 return $response;
1487 }
1488 )->setName('removeMembers')->add($authenticate);
1489
1490 $app->post(
1491 '/member/remove' . '[/{id:\d+}]',
1492 function ($request, $response) {
1493 $post = $request->getParsedBody();
1494 $ajax = isset($post['ajax']) && $post['ajax'] === 'true';
1495 $success = false;
1496
1497 $uri = isset($post['redirect_uri']) ?
1498 $post['redirect_uri'] :
1499 $this->router->pathFor('slash');
1500
1501 if (!isset($post['confirm'])) {
1502 $this->flash->addMessage(
1503 'error_detected',
1504 _T("Removal has not been confirmed!")
1505 );
1506 } else {
1507 if (isset($this->session->filter_members)) {
1508 $filters = $this->session->filter_members;
1509 } else {
1510 $filters = new MembersList();
1511 }
1512 $members = new Members($filters);
1513
1514 if (!is_array($post['id'])) {
1515 //delete member
1516 $adh = new Adherent($this->zdb, (int)$post['id']);
1517 $ids = (array)$post['id'];
1518 } else {
1519 $ids = $post['id'];
1520 }
1521
1522 $del = $members->removeMembers($ids);
1523
1524 if ($del !== true) {
1525 if (count($ids) === 1) {
1526 $error_detected = str_replace(
1527 '%name',
1528 $adh->sname,
1529 _T("An error occurred trying to remove member %name :/")
1530 );
1531 } else {
1532 $error_detected = _T("An error occurred trying to remove members :/");
1533 }
1534
1535 $this->flash->addMessage(
1536 'error_detected',
1537 $error_detected
1538 );
1539 } else {
1540 if (!is_array($post['id'])) {
1541 $success_detected = str_replace(
1542 '%name',
1543 $adh->sname,
1544 _T("Member %name has been successfully deleted.")
1545 );
1546 } else {
1547 $success_detected = str_replace(
1548 '%count',
1549 count($ids),
1550 _T("%count members have been successfully deleted.")
1551 );
1552 }
1553
1554 $this->flash->addMessage(
1555 'success_detected',
1556 $success_detected
1557 );
1558
1559 $success = true;
1560 }
1561 }
1562
1563 if (!$ajax) {
1564 return $response
1565 ->withStatus(301)
1566 ->withHeader('Location', $uri);
1567 } else {
1568 return $response->withJson(
1569 [
1570 'success' => $success
1571 ]
1572 );
1573 }
1574 }
1575 )->setName('doRemoveMember')->add($authenticate);
1576
1577 //advanced search page
1578 $app->get(
1579 '/advanced-search',
1580 function ($request, $response) {
1581 if (isset($this->session->filter_members)) {
1582 $filters = $this->session->filter_members;
1583 if (!$filters instanceof AdvancedMembersList) {
1584 $filters = new AdvancedMembersList($filters);
1585 }
1586 } else {
1587 $filters = new AdvancedMembersList();
1588 }
1589
1590 $groups = new Groups($this->zdb, $this->login);
1591 $groups_list = $groups->getList();
1592
1593 //we want only visibles fields
1594 $fields = $this->members_fields;
1595 $fc = $this->fields_config;
1596 $visibles = $fc->getVisibilities();
1597 $access_level = $this->login->getAccessLevel();
1598
1599 //remove not searchable fields
1600 unset($fields['mdp_adh']);
1601
1602 foreach ($fields as $k => $f) {
1603 if ($visibles[$k] == FieldsConfig::NOBODY ||
1604 ($visibles[$k] == FieldsConfig::ADMIN &&
1605 $access_level < Authentication::ACCESS_ADMIN) ||
1606 ($visibles[$k] == FieldsConfig::STAFF &&
1607 $access_level < Authentication::ACCESS_STAFF) ||
1608 ($visibles[$k] == FieldsConfig::MANAGER &&
1609 $access_level < Authentication::ACCESS_MANAGER)
1610 ) {
1611 unset($fields[$k]);
1612 }
1613 }
1614
1615 //add status label search
1616 if ($pos = array_search(Status::PK, array_keys($fields))) {
1617 $fields = array_slice($fields, 0, $pos, true) +
1618 ['status_label' => ['label' => _T('Status label')]] +
1619 array_slice($fields, $pos, count($fields) -1, true);
1620 }
1621
1622 //dynamic fields
1623 $deps = array(
1624 'picture' => false,
1625 'groups' => false,
1626 'dues' => false,
1627 'parent' => false,
1628 'children' => false,
1629 'dynamics' => false
1630 );
1631 $member = new Adherent($this->zdb, $this->login->login, $deps);
1632 $adh_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $member);
1633
1634 $contrib = new Contribution($this->zdb, $this->login);
1635 $contrib_dynamics = new DynamicFieldsHandle($this->zdb, $this->login, $contrib);
1636
1637 //Status
1638 $statuts = new Status($this->zdb);
1639
1640 //Contributions types
1641 $ct = new Galette\Entity\ContributionsTypes($this->zdb);
1642
1643 //Payments types
1644 $ptypes = new PaymentTypes(
1645 $this->zdb,
1646 $this->preferences,
1647 $this->login
1648 );
1649 $ptlist = $ptypes->getList();
1650
1651 $filters->setViewCommonsFilters($this->preferences, $this->view->getSmarty());
1652
1653 // display page
1654 $this->view->render(
1655 $response,
1656 'advanced_search.tpl',
1657 array(
1658 'page_title' => _T("Advanced search"),
1659 'require_dialog' => true,
1660 'require_calendar' => true,
1661 'require_sorter' => true,
1662 'filter_groups_options' => $groups_list,
1663 'search_fields' => $fields,
1664 'adh_dynamics' => $adh_dynamics->getFields(),
1665 'contrib_dynamics' => $contrib_dynamics->getFields(),
1666 'statuts' => $statuts->getList(),
1667 'contributions_types' => $ct->getList(),
1668 'filters' => $filters,
1669 'payments_types' => $ptlist
1670 )
1671 );
1672 return $response;
1673 }
1674 )->setName('advanced-search')->add($authenticate);
1675
1676 //Batch actions on members list
1677 $app->post(
1678 '/members/batch',
1679 function ($request, $response) {
1680 $post = $request->getParsedBody();
1681
1682 if (isset($post['member_sel'])) {
1683 if (isset($this->session->filter_members)) {
1684 $filters = $this->session->filter_members;
1685 } else {
1686 $filters = new MembersList();
1687 }
1688
1689 $filters->selected = $post['member_sel'];
1690 $this->session->filter_members = $filters;
1691
1692 if (isset($post['cards'])) {
1693 return $response
1694 ->withStatus(301)
1695 ->withHeader('Location', $this->router->pathFor('pdf-members-cards'));
1696 }
1697
1698 if (isset($post['labels'])) {
1699 return $response
1700 ->withStatus(301)
1701 ->withHeader('Location', $this->router->pathFor('pdf-members-labels'));
1702 }
1703
1704 if (isset($post['mailing'])) {
1705 return $response
1706 ->withStatus(301)
1707 ->withHeader('Location', $this->router->pathFor('mailing') . '?new=new');
1708 }
1709
1710 if (isset($post['attendance_sheet'])) {
1711 return $response
1712 ->withStatus(301)
1713 ->withHeader('Location', $this->router->pathFor('attendance_sheet_details'));
1714 }
1715
1716 if (isset($post['csv'])) {
1717 return $response
1718 ->withStatus(301)
1719 ->withHeader('Location', $this->router->pathFor('csv-memberslist'));
1720 }
1721
1722 if (isset($post['delete'])) {
1723 return $response
1724 ->withStatus(301)
1725 ->withHeader('Location', $this->router->pathFor('removeMembers'));
1726 }
1727
1728 if (isset($post['masschange'])) {
1729 return $response
1730 ->withStatus(301)
1731 ->withHeader('Location', $this->router->pathFor('masschangeMembers'));
1732 }
1733
1734 throw new \RuntimeException('Does not know what to batch :(');
1735 } else {
1736 $this->flash->addMessage(
1737 'error_detected',
1738 _T("No member was selected, please check at least one name.")
1739 );
1740
1741 return $response
1742 ->withStatus(301)
1743 ->withHeader('Location', $this->router->pathFor('members'));
1744 }
1745 }
1746 )->setName('batch-memberslist')->add($authenticate);
1747
1748 //PDF members cards
1749 $app->get(
1750 '/members/cards[/{' . Adherent::PK . ':\d+}]',
1751 function ($request, $response, $args) {
1752 if ($this->session->filter_members) {
1753 $filters = $this->session->filter_members;
1754 } else {
1755 $filters = new MembersList();
1756 }
1757
1758 if (isset($args[Adherent::PK])
1759 && $args[Adherent::PK] > 0
1760 ) {
1761 $id_adh = $args[Adherent::PK];
1762 $denied = false;
1763 if ($this->login->id != $id_adh
1764 && !$this->login->isAdmin()
1765 && !$this->login->isStaff()
1766 && !$this->login->isGroupManager()
1767 ) {
1768 $denied = true;
1769 }
1770
1771 if (!$this->login->isAdmin() && !$this->login->isStaff() && $this->login->id != $id_adh) {
1772 if ($this->login->isGroupManager()) {
1773 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true]);
1774 //check if current logged in user can manage loaded member
1775 $groups = $adh->groups;
1776 $can_manage = false;
1777 foreach ($groups as $group) {
1778 if ($this->login->isGroupManager($group->getId())) {
1779 $can_manage = true;
1780 break;
1781 }
1782 }
1783 if ($can_manage !== true) {
1784 Analog::log(
1785 'Logged in member ' . $this->login->login .
1786 ' has tried to load member #' . $adh->id .
1787 ' but do not manage any groups he belongs to.',
1788 Analog::WARNING
1789 );
1790 $denied = true;
1791 }
1792 } else {
1793 $denied = true;
1794 }
1795 }
1796
1797 if ($denied) {
1798 //requested member cannot be managed. Load logged in user
1799 $id_adh = (int)$this->login->id;
1800 }
1801
1802 //check if member is up to date
1803 if ($this->login->id == $id_adh) {
1804 $adh = new Adherent($this->zdb, (int)$id_adh, ['dues' => true]);
1805 if (!$adh->isUp2Date()) {
1806 Analog::log(
1807 'Member ' . $id_adh . ' is not up to date; cannot get his PDF member card',
1808 Analog::WARNING
1809 );
1810 return $response
1811 ->withStatus(301)
1812 ->withHeader('Location', $this->router->pathFor('slash'));
1813 }
1814 }
1815
1816 // If we are called from a member's card, get unique id value
1817 $unique = $id_adh;
1818 } else {
1819 if (count($filters->selected) == 0) {
1820 Analog::log(
1821 'No member selected to generate members cards',
1822 Analog::INFO
1823 );
1824 $this->flash->addMessage(
1825 'error_detected',
1826 _T("No member was selected, please check at least one name.")
1827 );
1828
1829 return $response
1830 ->withStatus(301)
1831 ->withHeader('Location', $this->router->pathFor('members'));
1832 }
1833 }
1834
1835 // Fill array $selected with selected ids
1836 $selected = array();
1837 if (isset($unique) && $unique) {
1838 $selected[] = $unique;
1839 } else {
1840 $selected = $filters->selected;
1841 }
1842
1843 $m = new Members();
1844 $members = $m->getArrayList(
1845 $selected,
1846 array('nom_adh', 'prenom_adh'),
1847 true
1848 );
1849
1850 if (!is_array($members) || count($members) < 1) {
1851 Analog::log(
1852 'An error has occurred, unable to get members list.',
1853 Analog::ERROR
1854 );
1855
1856 $this->flash->addMessage(
1857 'error_detected',
1858 _T("Unable to get members list.")
1859 );
1860
1861 return $response
1862 ->withStatus(301)
1863 ->withHeader('Location', $this->router->pathFor('members'));
1864 }
1865
1866 $pdf = new PdfMembersCards($this->preferences);
1867 $pdf->drawCards($members);
1868
1869 $response = $this->response->withHeader('Content-type', 'application/pdf')
1870 ->withHeader('Content-Disposition', 'attachment;filename="' . $pdf->getFileName() . '"');
1871 $response->write($pdf->download());
1872 return $response;
1873 }
1874 )->setName('pdf-members-cards')->add($authenticate);
1875
1876 //PDF members labels
1877 $app->get(
1878 '/members/labels',
1879 function ($request, $response) {
1880 $get = $request->getQueryParams();
1881
1882 if ($this->session->filter_reminders_labels) {
1883 $filters = $this->session->filter_reminders_labels;
1884 unset($this->session->filter_reminders_labels);
1885 } elseif ($this->session->filter_members) {
1886 $filters = $this->session->filter_members;
1887 } else {
1888 $filters = new MembersList();
1889 }
1890
1891 $members = null;
1892 if (isset($get['from'])
1893 && $get['from'] === 'mailing'
1894 ) {
1895 //if we're from mailing, we have to retrieve
1896 //its unreachables members for labels
1897 $mailing = $this->session->mailing;
1898 $members = $mailing->unreachables;
1899 } else {
1900 if (count($filters->selected) == 0) {
1901 Analog::log('No member selected to generate labels', Analog::INFO);
1902 $this->flash->addMessage(
1903 'error_detected',
1904 _T("No member was selected, please check at least one name.")
1905 );
1906
1907 return $response
1908 ->withStatus(301)
1909 ->withHeader('Location', $this->router->pathFor('members'));
1910 }
1911
1912 $m = new Members();
1913 $members = $m->getArrayList(
1914 $filters->selected,
1915 array('nom_adh', 'prenom_adh')
1916 );
1917 }
1918
1919 if (!is_array($members) || count($members) < 1) {
1920 Analog::log(
1921 'An error has occurred, unable to get members list.',
1922 Analog::ERROR
1923 );
1924
1925 $this->flash->addMessage(
1926 'error_detected',
1927 _T("Unable to get members list.")
1928 );
1929
1930 return $response
1931 ->withStatus(301)
1932 ->withHeader('Location', $this->router->pathFor('members'));
1933 }
1934
1935 $pdf = new PdfMembersLabels($this->preferences);
1936 $pdf->drawLabels($members);
1937 $response = $this->response->withHeader('Content-type', 'application/pdf')
1938 ->withHeader('Content-Disposition', 'attachment;filename="' . $pdf->getFileName() . '"');
1939 $response->write($pdf->download());
1940 return $response;
1941 }
1942 )->setName('pdf-members-labels')->add($authenticate);
1943
1944 //PDF adhesion form
1945 $app->get(
1946 '/members/adhesion-form/{' . Adherent::PK . ':\d+}',
1947 function ($request, $response, $args) {
1948 $id_adh = (int)$args[Adherent::PK];
1949
1950 $denied = false;
1951 if ($this->login->id != $args['id']
1952 && !$this->login->isAdmin()
1953 && !$this->login->isStaff()
1954 && !$this->login->isGroupManager()
1955 ) {
1956 $denied = true;
1957 }
1958
1959 if (!$this->login->isAdmin() && !$this->login->isStaff() && $this->login->id != $args['id']) {
1960 if ($this->login->isGroupManager()) {
1961 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true]);
1962 //check if current logged in user can manage loaded member
1963 $groups = $adh->groups;
1964 $can_manage = false;
1965 foreach ($groups as $group) {
1966 if ($this->login->isGroupManager($group->getId())) {
1967 $can_manage = true;
1968 break;
1969 }
1970 }
1971 if ($can_manage !== true) {
1972 Analog::log(
1973 'Logged in member ' . $this->login->login .
1974 ' has tried to load member #' . $adh->id .
1975 ' but do not manage any groups he belongs to.',
1976 Analog::WARNING
1977 );
1978 $denied = true;
1979 }
1980 } else {
1981 $denied = true;
1982 }
1983 }
1984
1985 if ($denied) {
1986 //requested member cannot be managed. Load logged in user
1987 $id_adh = (int)$this->login->id;
1988 }
1989 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true]);
1990
1991 $form = $this->preferences->pref_adhesion_form;
1992 $pdf = new $form($adh, $this->zdb, $this->preferences);
1993 $response = $this->response->withHeader('Content-type', 'application/pdf')
1994 ->withHeader('Content-Disposition', 'attachment; filename="' . $pdf->getFileName() . '"');
1995 $response->write($pdf->download());
1996 return $response;
1997 }
1998 )->setName('adhesionForm')->add($authenticate);
1999
2000 //Empty PDF adhesion form
2001 $app->get(
2002 '/members/empty-adhesion-form',
2003 function ($request, $response) {
2004 $form = $this->preferences->pref_adhesion_form;
2005 $pdf = new $form(null, $this->zdb, $this->preferences);
2006 $response = $this->response->withHeader('Content-type', 'application/pdf')
2007 ->withHeader('Content-Disposition', 'attachment; filename="' . $pdf->getFileName() . '"');
2008 $response->write($pdf->download());
2009 return $response;
2010 }
2011 )->setName('emptyAdhesionForm');
2012
2013 //mailing
2014 $app->get(
2015 '/mailing',
2016 function ($request, $response) {
2017 $get = $request->getQueryParams();
2018
2019 //We're done :-)
2020 if (isset($get['mailing_new'])
2021 || isset($get['reminder'])
2022 ) {
2023 if ($this->session->mailing !== null) {
2024 // check for temporary attachments to remove
2025 $m = $this->session->mailing;
2026 $m->removeAttachments(true);
2027 }
2028 $this->session->mailing = null;
2029 }
2030
2031 $params = array();
2032
2033 if ($this->preferences->pref_mail_method == Mailing::METHOD_DISABLED
2034 && !GALETTE_MODE === 'DEMO'
2035 ) {
2036 $this->history->add(
2037 _T("Trying to load mailing while mail is disabled in preferences.")
2038 );
2039 $this->flash->addMessage(
2040 'error_detected',
2041 _T("Trying to load mailing while mail is disabled in preferences.")
2042 );
2043 return $response
2044 ->withStatus(301)
2045 ->withHeader('Location', $this->router->pathFor('slash'));
2046 } else {
2047 if (isset($this->session->filter_mailing)) {
2048 $filters = $this->session->filter_mailing;
2049 } elseif (isset($this->session->filter_members)) {
2050 $filters = $this->session->filter_members;
2051 } else {
2052 $filters = new MembersList();
2053 }
2054
2055 if ($this->session->mailing !== null
2056 && !isset($get['from'])
2057 && !isset($get['reset'])
2058 ) {
2059 $mailing = $this->session->mailing;
2060 } elseif (isset($get['from']) && is_numeric($get['from'])) {
2061 $mailing = new Mailing($this->preferences, null, $get['from']);
2062 MailingHistory::loadFrom($this->zdb, (int)$get['from'], $mailing);
2063 } elseif (isset($get['reminder'])) {
2064 //FIXME: use a constant!
2065 $filters->reinit();
2066 $filters->membership_filter = Members::MEMBERSHIP_LATE;
2067 $filters->filter_account = Members::ACTIVE_ACCOUNT;
2068 $m = new Members($filters);
2069 $members = $m->getList(true);
2070 $mailing = new Mailing($this->preferences, ($members !== false) ? $members : null);
2071 } else {
2072 if (count($filters->selected) == 0
2073 && !isset($get['mailing_new'])
2074 && !isset($get['reminder'])
2075 ) {
2076 Analog::log(
2077 '[Mailings] No member selected for mailing',
2078 Analog::WARNING
2079 );
2080
2081 $this->flash->addMessage(
2082 'error_detected',
2083 _T('No member selected for mailing!')
2084 );
2085
2086 if (isset($profiler)) {
2087 $profiler->stop();
2088 }
2089
2090 $redirect_url = ($this->session->redirect_mailing !== null) ?
2091 $this->session->redirect_mailing :
2092 $this->router->pathFor('members');
2093
2094 return $response
2095 ->withStatus(301)
2096 ->withHeader('Location', $redirect_url);
2097 }
2098 $m = new Members();
2099 $members = $m->getArrayList($filters->selected);
2100 $mailing = new Mailing($this->preferences, ($members !== false) ? $members : null);
2101 }
2102
2103 if (isset($get['remove_attachment'])) {
2104 $mailing->removeAttachment($get['remove_attachment']);
2105 }
2106
2107 if ($mailing->current_step !== Mailing::STEP_SENT) {
2108 $this->session->mailing = $mailing;
2109 }
2110
2111 /** TODO: replace that... */
2112 $this->session->labels = $mailing->unreachables;
2113
2114 if (!$this->login->isSuperAdmin()) {
2115 $member = new Adherent($this->zdb, (int)$this->login->id, false);
2116 $params['sender_current'] = [
2117 'name' => $member->sname,
2118 'email' => $member->getEmail()
2119 ];
2120 }
2121
2122 $params = array_merge(
2123 $params,
2124 array(
2125 'mailing' => $mailing,
2126 'attachments' => $mailing->attachments,
2127 'html_editor' => true,
2128 'html_editor_active'=> $this->preferences->pref_editor_enabled
2129 )
2130 );
2131 }
2132
2133 // display page
2134 $this->view->render(
2135 $response,
2136 'mailing_adherents.tpl',
2137 array_merge(
2138 array(
2139 'page_title' => _T("Mailing"),
2140 'require_dialog' => true
2141 ),
2142 $params
2143 )
2144 );
2145 return $response;
2146 }
2147 )->setName('mailing')->add($authenticate);
2148
2149 $app->post(
2150 '/mailing',
2151 function ($request, $response) {
2152 $post = $request->getParsedBody();
2153 $error_detected = [];
2154 $success_detected = [];
2155
2156 $goto = $this->router->pathFor('mailings');
2157 $redirect_url = ($this->session->redirect_mailing !== null) ?
2158 $this->session->redirect_mailing :
2159 $this->router->pathFor('members');
2160
2161 //We're done :-)
2162 if (isset($post['mailing_done'])
2163 || isset($post['mailing_cancel'])
2164 ) {
2165 if ($this->session->mailing !== null) {
2166 // check for temporary attachments to remove
2167 $m = $this->session->mailing;
2168 $m->removeAttachments(true);
2169 }
2170 $this->session->mailing = null;
2171 if (isset($this->session->filter_mailing)) {
2172 $filters = $this->session->filter_mailing;
2173 $filters->selected = [];
2174 $this->session->filter_mailing = $filters;
2175 }
2176
2177 return $response
2178 ->withStatus(301)
2179 ->withHeader('Location', $redirect_url);
2180 }
2181
2182 $params = array();
2183
2184 if ($this->preferences->pref_mail_method == Mailing::METHOD_DISABLED
2185 && !GALETTE_MODE === 'DEMO'
2186 ) {
2187 $this->history->add(
2188 _T("Trying to load mailing while mail is disabled in preferences.")
2189 );
2190 $error_detected[] = _T("Trying to load mailing while mail is disabled in preferences.");
2191 $goto = $this->router->pathFor('slash');
2192 } else {
2193 if (isset($this->session->filter_members)) {
2194 $filters = $this->session->filter_members;
2195 } else {
2196 $filters = new MembersList();
2197 }
2198
2199 if ($this->session->mailing !== null
2200 && !isset($post['mailing_cancel'])
2201 ) {
2202 $mailing = $this->session->mailing;
2203 } else {
2204 if (count($filters->selected) == 0) {
2205 Analog::log(
2206 '[Mailings] No member selected for mailing',
2207 Analog::WARNING
2208 );
2209
2210 $this->flash->addMessage(
2211 'error_detected',
2212 _T('No member selected for mailing!')
2213 );
2214
2215 return $response
2216 ->withStatus(301)
2217 ->withHeader('Location', $redirect_url);
2218 }
2219 $m = new Members();
2220 $members = $m->getArrayList($filters->selected);
2221 $mailing = new Mailing($this->preferences, ($members !== false) ? $members : null);
2222 }
2223
2224 if (isset($post['mailing_go'])
2225 || isset($post['mailing_reset'])
2226 || isset($post['mailing_confirm'])
2227 || isset($post['mailing_save'])
2228 ) {
2229 if (trim($post['mailing_objet']) == '') {
2230 $error_detected[] = _T("Please type an object for the message.");
2231 } else {
2232 $mailing->subject = $post['mailing_objet'];
2233 }
2234
2235 if (trim($post['mailing_corps']) == '') {
2236 $error_detected[] = _T("Please enter a message.");
2237 } else {
2238 $mailing->message = $post['mailing_corps'];
2239 }
2240
2241 switch ($post['sender']) {
2242 case GaletteMail::SENDER_CURRENT:
2243 $member = new Adherent($this->zdb, (int)$this->login->id, false);
2244 $mailing->setSender(
2245 $member->sname,
2246 $member->getEmail()
2247 );
2248 break;
2249 case GaletteMail::SENDER_OTHER:
2250 $mailing->setSender(
2251 $post['sender_name'],
2252 $post['sender_address']
2253 );
2254 break;
2255 case GaletteMail::SENDER_PREFS:
2256 default:
2257 //nothing to do; this is the default :)
2258 break;
2259 }
2260
2261 $mailing->html = (isset($post['mailing_html'])) ? true : false;
2262
2263 //handle attachments
2264 if (isset($_FILES['files'])) {
2265 for ($i = 0; $i < count($_FILES['files']['name']); $i++) {
2266 if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) {
2267 if ($_FILES['files']['tmp_name'][$i] != '') {
2268 if (is_uploaded_file($_FILES['files']['tmp_name'][$i])) {
2269 $da_file = array();
2270 foreach (array_keys($_FILES['files']) as $key) {
2271 $da_file[$key] = $_FILES['files'][$key][$i];
2272 }
2273 $res = $mailing->store($da_file);
2274 if ($res < 0) {
2275 //what to do if one of attachments fail? should other be removed?
2276 $error_detected[] = $mailing->getAttachmentErrorMessage($res);
2277 }
2278 }
2279 }
2280 } elseif ($_FILES['files']['error'][$i] !== UPLOAD_ERR_NO_FILE) {
2281 Analog::log(
2282 $this->logo->getPhpErrorMessage($_FILES['files']['error'][$i]),
2283 Analog::WARNING
2284 );
2285 $error_detected[] = $this->logo->getPhpErrorMessage(
2286 $_FILES['files']['error'][$i]
2287 );
2288 }
2289 }
2290 }
2291
2292 if (count($error_detected) == 0
2293 && !isset($post['mailing_reset'])
2294 && !isset($post['mailing_save'])
2295 ) {
2296 $mailing->current_step = Mailing::STEP_PREVIEW;
2297 } else {
2298 $mailing->current_step = Mailing::STEP_START;
2299 }
2300 }
2301
2302 if (isset($post['mailing_confirm']) && count($error_detected) == 0) {
2303 $mailing->current_step = Mailing::STEP_SEND;
2304 //ok... let's go for fun
2305 $sent = $mailing->send();
2306 if ($sent == Mailing::MAIL_ERROR) {
2307 $mailing->current_step = Mailing::STEP_START;
2308 Analog::log(
2309 '[Mailings] Message was not sent. Errors: ' .
2310 print_r($mailing->errors, true),
2311 Analog::ERROR
2312 );
2313 foreach ($mailing->errors as $e) {
2314 $error_detected[] = $e;
2315 }
2316 } else {
2317 $mlh = new MailingHistory($this->zdb, $this->login, null, $mailing);
2318 $mlh->storeMailing(true);
2319 Analog::log(
2320 '[Mailings] Message has been sent.',
2321 Analog::INFO
2322 );
2323 $mailing->current_step = Mailing::STEP_SENT;
2324 //cleanup
2325 $filters->selected = null;
2326 $this->session->filter_members = $filters;
2327 $this->session->mailing = null;
2328 $success_detected[] = _T("Mailing has been successfully sent!");
2329 $goto = $redirect_url;
2330 }
2331 }
2332
2333 if ($mailing->current_step !== Mailing::STEP_SENT) {
2334 $this->session->mailing = $mailing;
2335 }
2336
2337 /** TODO: replace that... */
2338 $this->session->labels = $mailing->unreachables;
2339
2340 if (!isset($post['html_editor_active'])
2341 || trim($post['html_editor_active']) == ''
2342 ) {
2343 $post['html_editor_active'] = $this->preferences->pref_editor_enabled;
2344 }
2345
2346 if (isset($post['mailing_save'])) {
2347 //user requested to save the mailing
2348 $histo = new MailingHistory($this->zdb, $this->login, null, $mailing);
2349 if ($histo->storeMailing() !== false) {
2350 $success_detected[] = _T("Mailing has been successfully saved.");
2351 $this->session->mailing = null;
2352 }
2353 }
2354 }
2355
2356 //flash messages if any
2357 if (count($error_detected) > 0) {
2358 foreach ($error_detected as $error) {
2359 $this->flash->addMessage('error_detected', $error);
2360 }
2361 }
2362 if (count($success_detected) > 0) {
2363 foreach ($success_detected as $success) {
2364 $this->flash->addMessage('success_detected', $success);
2365 }
2366 }
2367
2368 return $response
2369 ->withStatus(301)
2370 ->withHeader('Location', $goto);
2371 }
2372 )->setName('doMailing')->add($authenticate);
2373
2374 $app->map(
2375 ['GET', 'POST'],
2376 '/mailing/preview[/{id:\d+}]',
2377 function ($request, $response, $args) {
2378 $post = $request->getParsedBody();
2379 // check for ajax mode
2380 $ajax = false;
2381 if ($request->isXhr()
2382 || isset($post['ajax'])
2383 && $post['ajax'] == 'true'
2384 ) {
2385 $ajax = true;
2386 }
2387
2388 $mailing = null;
2389 if (isset($args['id'])) {
2390 $mailing = new Mailing($this->preferences, null);
2391 MailingHistory::loadFrom($this->zdb, (int)$args['id'], $mailing, false);
2392 $attachments = $mailing->attachments;
2393 } else {
2394 $mailing = $this->session->mailing;
2395
2396 switch ($post['sender']) {
2397 case GaletteMail::SENDER_CURRENT:
2398 $member = new Adherent($this->zdb, (int)$this->login->id, false);
2399 $mailing->setSender(
2400 $member->sname,
2401 $member->getEmail()
2402 );
2403 break;
2404 case GaletteMail::SENDER_OTHER:
2405 $mailing->setSender(
2406 $post['sender_name'],
2407 $post['sender_address']
2408 );
2409 break;
2410 case GaletteMail::SENDER_PREFS:
2411 default:
2412 //nothing to do; this is the default :)
2413 break;
2414 }
2415
2416 $mailing->subject = $post['subject'];
2417 $mailing->message = $post['body'];
2418 $mailing->html = ($post['html'] === 'true');
2419 $attachments = (isset($post['attachments']) ? $post['attachments'] : []);
2420 }
2421
2422 // display page
2423 $this->view->render(
2424 $response,
2425 'mailing_preview.tpl',
2426 [
2427 'page_title' => _T("Mailing preview"),
2428 'mailing_id' => $args['id'],
2429 'mode' => ($ajax ? 'ajax' : ''),
2430 'mailing' => $mailing,
2431 'recipients' => $mailing->recipients,
2432 'sender' => $mailing->getSenderName() . ' &lt;' .
2433 $mailing->getSenderAddress() . '&gt;',
2434 'attachments' => $attachments
2435
2436 ]
2437 );
2438 return $response;
2439 }
2440 )->setName('mailingPreview')->add($authenticate);
2441
2442 $app->get(
2443 '/mailing/preview/{id:\d+}/attachment/{pos:\d+}',
2444 function ($request, $response, $args) {
2445 $mailing = new Mailing($this->preferences, null);
2446 MailingHistory::loadFrom($this->zdb, (int)$args['id'], $mailing, false);
2447 $attachments = $mailing->attachments;
2448 $attachment = $attachments[$args['pos']];
2449 $filepath = $attachment->getDestDir() . $attachment->getFileName();
2450
2451
2452 $ext = pathinfo($attachment->getFileName())['extension'];
2453 $response = $response->withHeader('Content-type', $attachment->getMimeType($filepath));
2454
2455 $body = $response->getBody();
2456 $body->write(file_get_contents($filepath));
2457 return $response;
2458 }
2459 )->setName('previewAttachment')->add($authenticate);
2460
2461 $app->post(
2462 '/ajax/mailing/set-recipients',
2463 function ($request, $response, $args) {
2464 $post = $request->getParsedBody();
2465 $mailing = $this->session->mailing;
2466
2467 $m = new Members();
2468
2469 $members = $m->getArrayList(
2470 $post['recipients'],
2471 null,
2472 false,
2473 true,
2474 null,
2475 false,
2476 false,
2477 true
2478 );
2479 $mailing->setRecipients($members);
2480
2481 $this->session->mailing = $mailing;
2482
2483 // display page
2484 $this->view->render(
2485 $response,
2486 'mailing_recipients.tpl',
2487 [
2488 'mailing' => $mailing
2489
2490 ]
2491 );
2492 return $response;
2493 }
2494 )->setName('mailingRecipients')->add($authenticate);
2495
2496 //reminders
2497 $app->get(
2498 '/reminders',
2499 function ($request, $response) {
2500 $texts = new Texts($this->texts_fields, $this->preferences, $this->router);
2501
2502 $previews = array(
2503 'impending' => $texts->getTexts('impendingduedate', $this->preferences->pref_lang),
2504 'late' => $texts->getTexts('lateduedate', $this->preferences->pref_lang)
2505 );
2506
2507 $members = new Members();
2508 $reminders = $members->getRemindersCount();
2509
2510 // display page
2511 $this->view->render(
2512 $response,
2513 'reminder.tpl',
2514 [
2515 'page_title' => _T("Reminders"),
2516 'previews' => $previews,
2517 'require_dialog' => true,
2518 'count_impending' => $reminders['impending'],
2519 'count_impending_nomail' => $reminders['nomail']['impending'],
2520 'count_late' => $reminders['late'],
2521 'count_late_nomail' => $reminders['nomail']['late']
2522 ]
2523 );
2524 return $response;
2525 }
2526 )->setName('reminders')->add($authenticate);
2527
2528 $app->post(
2529 '/reminders',
2530 function ($request, $response) {
2531 $error_detected = [];
2532 $warning_detected = [];
2533 $success_detected = [];
2534
2535 $post = $request->getParsedBody();
2536 $texts = new Texts($this->texts_fields, $this->preferences, $this->router);
2537 $selected = null;
2538 if (isset($post['reminders'])) {
2539 $selected = $post['reminders'];
2540 }
2541 $reminders = new Reminders($selected);
2542
2543 $labels = false;
2544 $labels_members = array();
2545 if (isset($post['reminder_wo_mail'])) {
2546 $labels = true;
2547 }
2548
2549 $list_reminders = $reminders->getList($this->zdb, $labels);
2550 if (count($list_reminders) == 0) {
2551 $warning_detected[] = _T("No reminder to send for now.");
2552 } else {
2553 foreach ($list_reminders as $reminder) {
2554 if ($labels === false) {
2555 //send reminders by mail
2556 $sent = $reminder->send($texts, $this->history, $this->zdb);
2557
2558 if ($sent === true) {
2559 $success_detected[] = $reminder->getMessage();
2560 } else {
2561 $error_detected[] = $reminder->getMessage();
2562 }
2563 } else {
2564 //generate labels for members without mail address
2565 $labels_members[] = $reminder->member_id;
2566 }
2567 }
2568
2569 if ($labels === true) {
2570 if (count($labels_members) > 0) {
2571 $labels_filters = new MembersList();
2572 $labels_filters->selected = $labels_members;
2573 $this->session->filters_reminders_labels = $labels_filters;
2574 return $response
2575 ->withStatus(301)
2576 ->withHeader('Location', $this->router->pathFor('pdf-member-labels'));
2577 } else {
2578 $error_detected[] = _T("There are no member to proceed.");
2579 }
2580 }
2581
2582 if (count($error_detected) > 0) {
2583 array_unshift(
2584 $error_detected,
2585 _T("Reminder has not been sent:")
2586 );
2587 }
2588
2589 if (count($success_detected) > 0) {
2590 array_unshift(
2591 $success_detected,
2592 _T("Sent reminders:")
2593 );
2594 }
2595 }
2596
2597 //flash messages if any
2598 if (count($error_detected) > 0) {
2599 foreach ($error_detected as $error) {
2600 $this->flash->addMessage('error_detected', $error);
2601 }
2602 }
2603 if (count($warning_detected) > 0) {
2604 foreach ($warning_detected as $warning) {
2605 $this->flash->addMessage('warning_detected', $warning);
2606 }
2607 }
2608 if (count($success_detected) > 0) {
2609 foreach ($success_detected as $success) {
2610 $this->flash->addMessage('success_detected', $success);
2611 }
2612 }
2613
2614 return $response
2615 ->withStatus(301)
2616 ->withHeader('Location', $this->router->pathFor('reminders'));
2617 }
2618 )->setName('doReminders')->add($authenticate);
2619
2620 $app->get(
2621 '/members/reminder-filter/{membership:nearly|late}/{mail:withmail|withoutmail}',
2622 function ($request, $response, $args) {
2623 //always reset filters
2624 $filters = new MembersList();
2625 $filters->filter_account = Members::ACTIVE_ACCOUNT;
2626
2627 $membership = ($args['membership'] === 'nearly' ?
2628 Members::MEMBERSHIP_NEARLY :
2629 Members::MEMBERSHIP_LATE);
2630 $filters->membership_filter = $membership;
2631
2632 //TODO: filter on reminder may take care of parent email as well
2633 $mail = ($args['mail'] === 'withmail' ?
2634 Members::FILTER_W_EMAIL :
2635 Members::FILTER_WO_EMAIL);
2636 $filters->email_filter = $mail;
2637
2638 $this->session->filter_members = $filters;
2639
2640 return $response
2641 ->withStatus(301)
2642 ->withHeader('Location', $this->router->pathFor('members'));
2643 }
2644 )->setName('reminders-filter')->add($authenticate);
2645
2646 $app->map(
2647 ['GET', 'POST'],
2648 '/attendance-sheet/details',
2649 function ($request, $response) {
2650 $post = $request->getParsedBody();
2651
2652 if ($this->session->filter_members !== null) {
2653 $filters = $this->session->filter_members;
2654 } else {
2655 $filters = new MembersList();
2656 }
2657
2658 // check for ajax mode
2659 $ajax = false;
2660 if ($request->isXhr()
2661 || isset($post['ajax'])
2662 && $post['ajax'] == 'true'
2663 ) {
2664 $ajax = true;
2665
2666 //retrieve selected members
2667 $selection = (isset($post['selection']) ) ? $post['selection'] : array();
2668
2669 $filters->selected = $selection;
2670 $this->session->filter_members = $filters;
2671 } else {
2672 $selection = $filters->selected;
2673 }
2674
2675
2676 // display page
2677 $this->view->render(
2678 $response,
2679 'attendance_sheet_details.tpl',
2680 [
2681 'page_title' => _T("Attendance sheet configuration"),
2682 'ajax' => $ajax,
2683 'selection' => $selection
2684 ]
2685 );
2686 return $response;
2687 }
2688 )->setName('attendance_sheet_details')->add($authenticate);
2689
2690 $app->post(
2691 '/attendance-sheet',
2692 function ($request, $response) {
2693 $post = $request->getParsedBody();
2694
2695 if ($this->session->filter_members !== null) {
2696 $filters = $this->session->filter_members;
2697 } else {
2698 $filters = new MembersList();
2699 }
2700
2701 //retrieve selected members
2702 $selection = (isset($post['selection']) ) ? $post['selection'] : array();
2703
2704 $filters->selected = $selection;
2705 $this->session->filter_members = $filters;
2706
2707 if (count($filters->selected) == 0) {
2708 Analog::log('No member selected to generate attendance sheet', Analog::INFO);
2709 $this->flash->addMessage(
2710 'error_detected',
2711 _T("No member selected to generate attendance sheet")
2712 );
2713
2714 return $response
2715 ->withStatus(301)
2716 ->withHeader('Location', $this->router->pathFor('members'));
2717 }
2718
2719 $m = new Members();
2720 $members = $m->getArrayList(
2721 $filters->selected,
2722 array('nom_adh', 'prenom_adh'),
2723 true
2724 );
2725
2726 if (!is_array($members) || count($members) < 1) {
2727 Analog::log('No member selected to generate attendance sheet', Analog::INFO);
2728 $this->flash->addMessage(
2729 'error_detected',
2730 _T("No member selected to generate attendance sheet")
2731 );
2732
2733 return $response
2734 ->withStatus(301)
2735 ->withHeader('Location', $this->router->pathFor('members'));
2736 }
2737
2738 $doc_title = _T("Attendance sheet");
2739 if (isset($post['sheet_type']) && trim($post['sheet_type']) != '') {
2740 $doc_title = $post['sheet_type'];
2741 }
2742
2743 $data = [
2744 'doc_title' => $doc_title,
2745 'title' => $post['sheet_title'] ?? null,
2746 'subtitle' => $post['sheet_sub_title'] ?? null,
2747 'sheet_date'=> $post['sheet_date'] ?? null
2748 ];
2749 $pdf = new Galette\IO\PdfAttendanceSheet($this->zdb, $this->preferences, $data);
2750 //with or without images?
2751 if (isset($post['sheet_photos']) && $post['sheet_photos'] === '1') {
2752 $pdf->withImages();
2753 }
2754 $pdf->drawSheet($members);
2755 $response = $this->response->withHeader('Content-type', 'application/pdf');
2756 $response->write($pdf->Output(_T("attendance_sheet") . '.pdf', 'D'));
2757 return $response;
2758 }
2759 )->setName('attendance_sheet')->add($authenticate);
2760
2761 $app->post(
2762 '/ajax/members[/{option:page|order}/{value:\d+}]',
2763 function ($request, $response, $args) {
2764 $post = $request->getParsedBody();
2765
2766 if (isset($this->session->ajax_members_filters)) {
2767 $filters = $this->session->ajax_members_filters;
2768 } else {
2769 $filters = new MembersList();
2770 }
2771
2772 if (isset($args['option']) && $args['option'] == 'page') {
2773 $filters->current_page = (int)$args['value'];
2774 }
2775
2776 //numbers of rows to display
2777 if (isset($post['nbshow']) && is_numeric($post['nbshow'])) {
2778 $filters->show = $post['nbshow'];
2779 }
2780
2781 $members = new Members($filters);
2782 if (!$this->login->isAdmin() && !$this->login->isStaff()) {
2783 if ($this->login->isGroupManager()) {
2784 $members_list = $members->getManagedMembersList(true);
2785 } else {
2786 Analog::log(
2787 str_replace(
2788 ['%id', '%login'],
2789 [$this->login->id, $this->login->login],
2790 'Trying to list group members without access from #%id (%login)'
2791 ),
2792 Analog::ERROR
2793 );
2794 throw new Exception('Access denied.');
2795 exit(0);
2796 }
2797 } else {
2798 $members_list = $members->getMembersList(true);
2799 }
2800
2801 //assign pagination variables to the template and add pagination links
2802 $filters->setSmartyPagination($this->router, $this->view->getSmarty(), false);
2803
2804 $this->session->ajax_members_filters = $filters;
2805
2806 $selected_members = null;
2807 $unreachables_members = null;
2808 if (!isset($post['from'])) {
2809 $mailing = $this->session->mailing;
2810 if (!isset($post['members'])) {
2811 $selected_members = $mailing->recipients;
2812 $unreachables_members = $mailing->unreachables;
2813 } else {
2814 $m = new Members();
2815 $selected_members = $m->getArrayList($post['members']);
2816 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
2817 $unreachables_members = $m->getArrayList($post['unreachables']);
2818 }
2819 }
2820 } else {
2821 switch ($post['from']) {
2822 case 'groups':
2823 if (!isset($post['gid'])) {
2824 Analog::log(
2825 'Trying to list group members with no group id provided',
2826 Analog::ERROR
2827 );
2828 throw new Exception('A group id is required.');
2829 exit(0);
2830 }
2831 if (!isset($post['members'])) {
2832 $group = new Group((int)$post['gid']);
2833 $selected_members = array();
2834 if (!isset($post['mode']) || $post['mode'] == 'members') {
2835 $selected_members = $group->getMembers();
2836 } elseif ($post['mode'] == 'managers') {
2837 $selected_members = $group->getManagers();
2838 } else {
2839 Analog::log(
2840 'Trying to list group members with unknown mode',
2841 Analog::ERROR
2842 );
2843 throw new Exception('Unknown mode.');
2844 exit(0);
2845 }
2846 } else {
2847 $m = new Members();
2848 $selected_members = $m->getArrayList($post['members']);
2849 if (isset($post['unreachables']) && is_array($post['unreachables'])) {
2850 $unreachables_members = $m->getArrayList($post['unreachables']);
2851 }
2852 }
2853 break;
2854 case 'attach':
2855 if (!isset($post['id_adh'])) {
2856 throw new \RuntimeException(
2857 'Current selected member must be excluded while attaching!'
2858 );
2859 exit(0);
2860 }
2861 break;
2862 }
2863 }
2864
2865 $params = [
2866 'filters' => $filters,
2867 'members_list' => $members_list,
2868 'selected_members' => $selected_members,
2869 'unreachables_members' => $unreachables_members
2870 ];
2871
2872 if (isset($post['multiple'])) {
2873 $params['multiple'] = true;
2874 }
2875
2876 if (isset($post['gid'])) {
2877 $params['the_id'] = $post['gid'];
2878 }
2879
2880 if (isset($post['id_adh'])) {
2881 $params['excluded'] = $post['id_adh'];
2882 }
2883
2884 // display page
2885 $this->view->render(
2886 $response,
2887 'ajax_members.tpl',
2888 $params
2889 );
2890 return $response;
2891 }
2892 )->setName('ajaxMembers')->add($authenticate);
2893
2894 $app->post(
2895 '/ajax/group/members',
2896 function ($request, $response) {
2897 $post = $request->getParsedBody();
2898
2899 $ids = $post['persons'];
2900 $mode = $post['person_mode'];
2901
2902 if (!$ids || !$mode) {
2903 Analog::log(
2904 'Missing persons and mode for ajaxGroupMembers',
2905 Analog::INFO
2906 );
2907 die();
2908 }
2909
2910 $m = new Members;
2911 $persons = $m->getArrayList($ids);
2912
2913 // display page
2914 $this->view->render(
2915 $response,
2916 'group_persons.tpl',
2917 [
2918 'persons' => $persons,
2919 'person_mode' => $mode
2920 ]
2921 );
2922 return $response;
2923 }
2924 )->setName('ajaxGroupMembers')->add($authenticate);
2925
2926 $app->get(
2927 '/member/{id:\d+}/file/{fid:\d+}/{pos:\d+}/{name}',
2928 function ($request, $response, $args) {
2929 $denied = false;
2930 $id = (int)$args['id'];
2931 if ($this->login->id != $args['id']
2932 && !$this->login->isAdmin()
2933 && !$this->login->isStaff()
2934 && !$this->login->isGroupManager()
2935 ) {
2936 $denied = true;
2937 }
2938
2939 $deps = array(
2940 'picture' => false,
2941 'groups' => false,
2942 'dues' => false,
2943 'parent' => false,
2944 'children' => false,
2945 'dynamics' => true
2946 );
2947 $member = new Adherent($this->zdb, $id, $deps);
2948
2949 if (!$denied && $this->login->id != $args['id']
2950 && $this->login->isGroupManager()
2951 && !$this->login->isStaff()
2952 && !$this->login->isAdmin()
2953 ) {
2954 //check if current logged in user can manage loaded member
2955 $groups = $member->groups;
2956 $can_manage = false;
2957 foreach ($groups as $group) {
2958 if ($this->login->isGroupManager($group->getId())) {
2959 $can_manage = true;
2960 break;
2961 }
2962 }
2963 if ($can_manage !== true) {
2964 Analog::log(
2965 'Logged in member ' . $this->login->login .
2966 ' has tried to load member #' . $member->id .
2967 ' but do not manage any groups he belongs to.',
2968 Analog::WARNING
2969 );
2970 $denied = true;
2971 }
2972 }
2973
2974 if ($denied === false) {
2975 $fields = $member->getDynamicFields()->getFields();
2976 if (!isset($fields[$args['fid']])) {
2977 //field does not exists or access is forbidden
2978 $denied = true;
2979 }
2980 }
2981
2982 if ($denied === true) {
2983 $this->flash->addMessage(
2984 'error_detected',
2985 _T("You do not have permission for requested URL.")
2986 );
2987
2988 return $response
2989 ->withStatus(403)
2990 ->withHeader(
2991 'Location',
2992 $this->router->pathFor(
2993 'member',
2994 ['id' => $id]
2995 )
2996 );
2997 }
2998
2999 $filename = str_replace(
3000 [
3001 '%mid',
3002 '%fid',
3003 '%pos'
3004 ],
3005 [
3006 $args['id'],
3007 $args['fid'],
3008 $args['pos']
3009 ],
3010 'member_%mid_field_%fid_value_%pos'
3011 );
3012
3013 if (file_exists(GALETTE_FILES_PATH . $filename)) {
3014 $type = File::getMimeType(GALETTE_FILES_PATH . $filename);
3015 $response = $this->response
3016 ->withHeader('Content-Type', $type)
3017 ->withHeader('Content-Disposition', 'attachment;filename="' . $args['name'] . '"')
3018 ->withHeader('Pragma', 'no-cache');
3019 $response->write(readfile(GALETTE_FILES_PATH . $filename));
3020 return $response;
3021 } else {
3022 Analog::log(
3023 'A request has been made to get an exported file named `' .
3024 $filename .'` that does not exists.',
3025 Analog::WARNING
3026 );
3027
3028 $this->flash->addMessage(
3029 'error_detected',
3030 _T("The file does not exists or cannot be read :(")
3031 );
3032
3033 return $response
3034 ->withStatus(404)
3035 ->withHeader(
3036 'Location',
3037 $this->router->pathFor('member', ['id' => $args['id']])
3038 );
3039 }
3040 }
3041 )->setName('getDynamicFile')->add($authenticate);
3042
3043 $app->get(
3044 '/members/mass-change',
3045 function ($request, $response) {
3046 $filters = $this->session->filter_members;
3047
3048 $data = [
3049 'id' => $filters->selected,
3050 'redirect_uri' => $this->router->pathFor('members')
3051 ];
3052
3053 $fc = $this->fields_config;
3054 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
3055
3056 //dynamic fields
3057 $deps = array(
3058 'picture' => false,
3059 'groups' => false,
3060 'dues' => false,
3061 'parent' => false,
3062 'children' => false,
3063 'dynamics' => false
3064 );
3065 $member = new Adherent($this->zdb, null, $deps);
3066
3067 //Status
3068 $statuts = new Status($this->zdb);
3069
3070 // display page
3071 $this->view->render(
3072 $response,
3073 'mass_change_members.tpl',
3074 array(
3075 'mode' => $request->isXhr() ? 'ajax' : '',
3076 'page_title' => str_replace(
3077 '%count',
3078 count($data['id']),
3079 _T('Mass change %count members')
3080 ),
3081 'form_url' => $this->router->pathFor('masschangeMembersReview'),
3082 'cancel_uri' => $this->router->pathFor('members'),
3083 'data' => $data,
3084 'member' => $member,
3085 'fieldsets' => $form_elements['fieldsets'],
3086 'titles_list' => Titles::getList($this->zdb),
3087 'statuts' => $statuts->getList(),
3088 'require_mass' => true
3089 )
3090 );
3091 return $response;
3092 }
3093 )->setName('masschangeMembers')->add($authenticate);
3094
3095 $app->post(
3096 '/members/mass-change/validate',
3097 function ($request, $response) {
3098 $post = $request->getParsedBody();
3099
3100 if (!isset($post['confirm'])) {
3101 $this->flash->addMessage(
3102 'error_detected',
3103 _T("Mass changes has not been confirmed!")
3104 );
3105 } else {
3106 //we want only visibles fields
3107 $fc = $this->fields_config;
3108 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
3109
3110 $changes = [];
3111 foreach ($form_elements['fieldsets'] as $form_element) {
3112 foreach ($form_element->elements as $field) {
3113 if (isset($post[$field->field_id]) && isset($post['mass_' . $field->field_id])) {
3114 $changes[$field->field_id] = [
3115 'label' => $field->label,
3116 'value' => $post[$field->field_id]
3117 ];
3118 }
3119 }
3120 }
3121 }
3122
3123 $filters = $this->session->filter_members;
3124 $data = [
3125 'id' => $filters->selected,
3126 'redirect_uri' => $this->router->pathFor('members')
3127 ];
3128
3129 //Status
3130 $statuts = new Status($this->zdb);
3131
3132 // display page
3133 $this->view->render(
3134 $response,
3135 'mass_change_members.tpl',
3136 array(
3137 'mode' => $request->isXhr() ? 'ajax' : '',
3138 'page_title' => str_replace(
3139 '%count',
3140 count($data['id']),
3141 _T('Review mass change %count members')
3142 ),
3143 'form_url' => $this->router->pathFor('massstoremembers'),
3144 'cancel_uri' => $this->router->pathFor('members'),
3145 'data' => $data,
3146 'titles_list' => Titles::getList($this->zdb),
3147 'statuts' => $statuts->getList(),
3148 'changes' => $changes
3149 )
3150 );
3151 return $response;
3152 }
3153 )->setName('masschangeMembersReview')->add($authenticate);
3154
3155 $app->post(
3156 '/members/mass-change',
3157 function ($request, $response, $args) {
3158 $post = $request->getParsedBody();
3159 $redirect_url = $post['redirect_uri'];
3160 $error_detected = [];
3161 $mass = 0;
3162
3163 unset($post['redirect_uri']);
3164 if (!isset($post['confirm'])) {
3165 $error_detected[] = _T("Mass changes has not been confirmed!");
3166 } else {
3167 unset($post['confirm']);
3168 $ids = $post['id'];
3169 unset($post['id']);
3170
3171 $fc = $this->fields_config;
3172 $form_elements = $fc->getMassiveFormElements($this->members_fields, $this->login);
3173 $disabled = $this->members_fields;
3174 foreach (array_keys($post) as $key) {
3175 $found = false;
3176 foreach ($form_elements['fieldsets'] as $fieldset) {
3177 if (isset($fieldset->elements[$key])) {
3178 $found = true;
3179 continue;
3180 }
3181 }
3182 if (!$found) {
3183 Analog::log(
3184 'Permission issue mass editing field ' . $key,
3185 Analog::WARNING
3186 );
3187 unset($post[$key]);
3188 } else {
3189 unset($disabled[$key]);
3190 }
3191 }
3192
3193 if (!count($post)) {
3194 $error_detected[] = _T("Nothing to do!");
3195 } else {
3196 $is_manager = !$this->login->isAdmin() && !$this->login->isStaff() && $this->login->isGroupManager();
3197 foreach ($ids as $id) {
3198 $deps = array(
3199 'picture' => false,
3200 'groups' => $is_manager,
3201 'dues' => false,
3202 'parent' => false,
3203 'children' => false,
3204 'dynamics' => false
3205 );
3206 $member = new Adherent($this->zdb, (int)$id, $deps);
3207 $member->setDependencies(
3208 $this->preferences,
3209 $this->members_fields,
3210 $this->history
3211 );
3212
3213 if ($is_manager) {
3214 $groups = $member->groups;
3215 $is_managed = false;
3216 foreach ($groups as $g) {
3217 if ($this->login->isGroupManager($g->getId())) {
3218 $is_managed = true;
3219 break;
3220 }
3221 }
3222 if (!$is_managed) {
3223 Analog::log(
3224 'Trying to edit member #' . $id . ' without appropriate ACLs',
3225 Analog::WARNING
3226 );
3227 $error_detected[] = _T('No permission to edit member');
3228 continue;
3229 }
3230 }
3231
3232 $valid = $member->check($post, [], $disabled);
3233 if ($valid === true) {
3234 $done = $member->store();
3235 if (!$done) {
3236 $error_detected[] = _T("An error occurred while storing the member.");
3237 } else {
3238 ++$mass;
3239 }
3240 } else {
3241 $error_detected = array_merge($error_detected, $valid);
3242 }
3243 }
3244 }
3245 }
3246
3247 if ($mass == 0 && !count($error_detected)) {
3248 $error_detected[] = _T('Something went wront during mass edition!');
3249 } else {
3250 $this->flash->addMessage(
3251 'success_detected',
3252 str_replace(
3253 '%count',
3254 $mass,
3255 _T('%count members has been changed successfully!')
3256 )
3257 );
3258 }
3259
3260 if (count($error_detected) > 0) {
3261 foreach ($error_detected as $error) {
3262 $this->flash->addMessage(
3263 'error_detected',
3264 $error
3265 );
3266 }
3267 }
3268
3269 if (!$request->isXhr()) {
3270 return $response
3271 ->withStatus(301)
3272 ->withHeader('Location', $redirect_url);
3273 } else {
3274 return $response->withJson(
3275 [
3276 'success' => count($error_detected) === 0
3277 ]
3278 );
3279 }
3280 }
3281 )->setName('massstoremembers')->add($authenticate);
3282
3283 //Duplicate member
3284 $app->get(
3285 '/members/duplicate/{' . Adherent::PK . ':\d+}',
3286 function ($request, $response, $args) {
3287 $id_adh = (int)$args[Adherent::PK];
3288 $adh = new Adherent($this->zdb, $id_adh, ['dynamics' => true]);
3289 $adh->setDuplicate();
3290
3291 //store entity in session
3292 $this->session->member = $adh;
3293
3294 return $response
3295 ->withStatus(301)
3296 ->withHeader('Location', $this->router->pathFor('editmember', ['action' => 'add']));
3297 }
3298 )->setName('duplicateMember')->add($authenticate);
3299
3300 //saved searches
3301 $app->map(
3302 ['GET', 'POST'],
3303 '/save-search',
3304 function ($request, $response) {
3305 if ($request->isPost()) {
3306 $post = $request->getParsedBody();
3307 } else {
3308 $post = $request->getQueryParams();
3309 }
3310
3311 $name = null;
3312 if (isset($post['search_title'])) {
3313 $name = $post['search_title'];
3314 unset($post['search_title']);
3315 }
3316
3317 //when using advanced search, no parameters are sent
3318 if (isset($post['advanced_search'])) {
3319 $post = [];
3320 $filters = $this->session->filter_members;
3321 foreach ($filters->search_fields as $field) {
3322 $post[$field] = $filters->$field;
3323 }
3324 }
3325
3326 //reformat, add required infos
3327 $post = [
3328 'parameters' => $post,
3329 'form' => 'Adherent',
3330 'name' => $name
3331 ];
3332
3333 $sco = new Galette\Entity\SavedSearch($this->zdb, $this->login);
3334 if ($check = $sco->check($post)) {
3335 if (!$res = $sco->store()) {
3336 if ($res === false) {
3337 $this->flash->addMessage(
3338 'error_detected',
3339 _T("An SQL error has occurred while storing search.")
3340 );
3341 } else {
3342 $this->flash->addMessage(
3343 'warning_detected',
3344 _T("This search is already saved.")
3345 );
3346 }
3347 } else {
3348 $this->flash->addMessage(
3349 'success_detected',
3350 _T("Search has been saved.")
3351 );
3352 }
3353 } else {
3354 //report errors
3355 foreach ($sco->getErrors() as $error) {
3356 $this->flash->addMessage(
3357 'error_detected',
3358 $error
3359 );
3360 }
3361 }
3362
3363 if ($request->isGet()) {
3364 return $response
3365 ->withStatus(301)
3366 ->withHeader('Location', $this->router->pathFor('members'));
3367 }
3368 }
3369 )->setName('saveSearch');
3370
3371 $app->get(
3372 '/saved-searches[/{option:page|order}/{value:\d+}]',
3373 function ($request, $response, $args = []) {
3374 $option = null;
3375 if (isset($args['option'])) {
3376 $option = $args['option'];
3377 }
3378 $value = null;
3379 if (isset($args['value'])) {
3380 $value = $args['value'];
3381 }
3382
3383 if (isset($this->session->filter_savedsearch)) {
3384 $filters = $this->session->filter_savedsearch;
3385 } else {
3386 $filters = new SavedSearchesList();
3387 }
3388
3389 if ($option !== null) {
3390 switch ($option) {
3391 case 'page':
3392 $filters->current_page = (int)$value;
3393 break;
3394 case 'order':
3395 $filters->orderby = $value;
3396 break;
3397 }
3398 }
3399
3400 $searches = new SavedSearches($this->zdb, $this->login, $filters);
3401 $list = $searches->getList(true);
3402
3403 //assign pagination variables to the template and add pagination links
3404 $filters->setSmartyPagination($this->router, $this->view->getSmarty(), false);
3405
3406 $this->session->filter_savedsearch = $filters;
3407
3408 // display page
3409 $this->view->render(
3410 $response,
3411 'saved_searches.tpl',
3412 array(
3413 'page_title' => _T("Saved searches"),
3414 'require_dialog' => true,
3415 'searches' => $list,
3416 'nb' => $searches->getCount(),
3417 'filters' => $filters
3418 )
3419 );
3420 return $response;
3421 }
3422 )->setName('searches')->add($authenticate);
3423
3424 $app->get(
3425 '/search/remove/{id:\d+}',
3426 function ($request, $response, $args) {
3427 $data = [
3428 'id' => $args['id'],
3429 'redirect_uri' => $this->router->pathFor('searches')
3430 ];
3431
3432 // display page
3433 $this->view->render(
3434 $response,
3435 'confirm_removal.tpl',
3436 array(
3437 'type' => _T("Saved search"),
3438 'mode' => $request->isXhr() ? 'ajax' : '',
3439 'page_title' => _T('Remove saved search'),
3440 'form_url' => $this->router->pathFor('doRemoveSearch', ['id' => $args['id']]),
3441 'cancel_uri' => $this->router->pathFor('searches'),
3442 'data' => $data
3443 )
3444 );
3445 return $response;
3446 }
3447 )->setName('removeSearch')->add($authenticate);
3448
3449 $app->get(
3450 '/searches/remove',
3451 function ($request, $response) {
3452 $filters = $this->session->filter_savedsearch;
3453
3454 $data = [
3455 'id' => $filters->selected,
3456 'redirect_uri' => $this->router->pathFor('searches')
3457 ];
3458
3459 // display page
3460 $this->view->render(
3461 $response,
3462 'confirm_removal.tpl',
3463 array(
3464 'type' => _T("Saved search"),
3465 'mode' => $request->isXhr() ? 'ajax' : '',
3466 'page_title' => _T('Remove saved searches'),
3467 'message' => str_replace(
3468 '%count',
3469 count($data['id']),
3470 _T('You are about to remove %count searches.')
3471 ),
3472 'form_url' => $this->router->pathFor('doRemoveSearch'),
3473 'cancel_uri' => $this->router->pathFor('searches'),
3474 'data' => $data
3475 )
3476 );
3477 return $response;
3478 }
3479 )->setName('removeSearches')->add($authenticate);
3480
3481 $app->post(
3482 '/search/remove' . '[/{id:\d+}]',
3483 function ($request, $response) {
3484 $post = $request->getParsedBody();
3485 $ajax = isset($post['ajax']) && $post['ajax'] === 'true';
3486 $success = false;
3487
3488 $uri = isset($post['redirect_uri']) ?
3489 $post['redirect_uri'] :
3490 $this->router->pathFor('slash');
3491
3492 if (!isset($post['confirm'])) {
3493 $this->flash->addMessage(
3494 'error_detected',
3495 _T("Removal has not been confirmed!")
3496 );
3497 } else {
3498 if (isset($this->session->filter_savedsearch)) {
3499 $filters = $this->session->filter_savedsearch;
3500 } else {
3501 $filters = new SavedSearchesList();
3502 }
3503 $searches = new SavedSearches($this->zdb, $this->login, $filters);
3504
3505 if (!is_array($post['id'])) {
3506 $ids = (array)$post['id'];
3507 } else {
3508 $ids = $post['id'];
3509 }
3510
3511 $del = $searches->remove($ids, $this->history);
3512
3513 if ($del !== true) {
3514 $error_detected = _T("An error occurred trying to remove searches :/");
3515
3516 $this->flash->addMessage(
3517 'error_detected',
3518 $error_detected
3519 );
3520 } else {
3521 $success_detected = str_replace(
3522 '%count',
3523 count($ids),
3524 _T("%count searches have been successfully deleted.")
3525 );
3526
3527 $this->flash->addMessage(
3528 'success_detected',
3529 $success_detected
3530 );
3531
3532 $success = true;
3533 }
3534 }
3535
3536 if (!$ajax) {
3537 return $response
3538 ->withStatus(301)
3539 ->withHeader('Location', $uri);
3540 } else {
3541 return $response->withJson(
3542 [
3543 'success' => $success
3544 ]
3545 );
3546 }
3547 }
3548 )->setName('doRemoveSearch')->add($authenticate);
3549
3550 $app->get(
3551 '/save-search/{id}',
3552 function ($request, $response, $args) {
3553 try {
3554 $sco = new Galette\Entity\SavedSearch($this->zdb, $this->login, (int)$args['id']);
3555 $this->flash->addMessage(
3556 'success_detected',
3557 _T("Saved search loaded")
3558 );
3559 } catch (\Exception $e) {
3560 $this->flash->addMessage(
3561 'error_detected',
3562 _T("An SQL error has occurred while storing search.")
3563 );
3564 }
3565 $parameters = (array)$sco->parameters;
3566
3567 $filters = null;
3568 if (isset($parameters['free_search'])) {
3569 $filters = new AdvancedMembersList();
3570 } else {
3571 $filters = new MembersList();
3572 }
3573
3574 foreach ($parameters as $key => $value) {
3575 $filters->$key = $value;
3576 }
3577 $this->session->filter_members = $filters;
3578
3579 return $response
3580 ->withStatus(301)
3581 ->withHeader('Location', $this->router->pathFor('members'));
3582 }
3583 )->setName('loadSearch');