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