]> git.agnieray.net Git - galette.git/blob - galette/lib/Galette/Repository/Members.php
Use events to send contributions administrative emails
[galette.git] / galette / lib / Galette / Repository / Members.php
1 <?php
2
3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5 /**
6 * Members class
7 *
8 * PHP version 5
9 *
10 * Copyright © 2009-2014 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 Repository
28 * @package Galette
29 *
30 * @author Johan Cwiklinski <johan@x-tnd.be>
31 * @copyright 2009-2014 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 * @link http://galette.tuxfamily.org
34 * @since Available since 0.7dev - 2009-02-28
35 */
36
37 namespace Galette\Repository;
38
39 use Galette\DynamicFields\DynamicField;
40 use Galette\Entity\DynamicFieldsHandle;
41 use Analog\Analog;
42 use Laminas\Db\Adapter\Adapter;
43 use Laminas\Db\Sql\Expression;
44 use Laminas\Db\Sql\Select;
45 use Laminas\Db\Sql\Predicate\PredicateSet;
46 use Laminas\Db\Sql\Predicate\Operator;
47 use Galette\Entity\Adherent;
48 use Galette\Entity\Contribution;
49 use Galette\Entity\Transaction;
50 use Galette\Entity\Reminder;
51 use Galette\Filters\MembersList;
52 use Galette\Filters\AdvancedMembersList;
53 use Galette\Core\Picture;
54 use Galette\Entity\Group;
55 use Galette\Repository\Groups;
56 use Galette\Entity\Status;
57 use Galette\Core\Db;
58
59 /**
60 * Members class for galette
61 *
62 * @name Members
63 * @category Repository
64 * @package Galette
65 *
66 * @author Johan Cwiklinski <johan@x-tnd.be>
67 * @copyright 2009-2014 The Galette Team
68 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
69 * @link http://galette.tuxfamily.org
70 */
71 class Members
72 {
73 public const TABLE = Adherent::TABLE;
74 public const PK = Adherent::PK;
75
76 public const ALL_ACCOUNTS = 0;
77 public const ACTIVE_ACCOUNT = 1;
78 public const INACTIVE_ACCOUNT = 2;
79
80 public const SHOW_LIST = 0;
81 public const SHOW_PUBLIC_LIST = 1;
82 public const SHOW_ARRAY_LIST = 2;
83 public const SHOW_STAFF = 3;
84 public const SHOW_MANAGED = 4;
85 public const SHOW_EXPORT = 5;
86
87 public const FILTER_NAME = 0;
88 public const FILTER_ADDRESS = 1;
89 public const FILTER_MAIL = 2;
90 public const FILTER_JOB = 3;
91 public const FILTER_INFOS = 4;
92 public const FILTER_DC_EMAIL = 5;
93 public const FILTER_W_EMAIL = 6;
94 public const FILTER_WO_EMAIL = 7;
95 public const FILTER_COMPANY_NAME = 8;
96 public const FILTER_DC_PUBINFOS = 9;
97 public const FILTER_W_PUBINFOS = 10;
98 public const FILTER_WO_PUBINFOS = 11;
99 public const FILTER_NUMBER = 12;
100
101 public const MEMBERSHIP_ALL = 0;
102 public const MEMBERSHIP_UP2DATE = 3;
103 public const MEMBERSHIP_NEARLY = 1;
104 public const MEMBERSHIP_LATE = 2;
105 public const MEMBERSHIP_NEVER = 4;
106 public const MEMBERSHIP_STAFF = 5;
107 public const MEMBERSHIP_ADMIN = 6;
108 public const MEMBERSHIP_NONE = 7;
109
110 public const ORDERBY_NAME = 0;
111 public const ORDERBY_NICKNAME = 1;
112 public const ORDERBY_STATUS = 2;
113 public const ORDERBY_FEE_STATUS = 3;
114 public const ORDERBY_MODIFDATE = 4;
115 public const ORDERBY_ID = 5;
116
117 public const NON_STAFF_MEMBERS = 30;
118
119 private $filters = false;
120 private $count = null;
121 private $errors = array();
122
123 /**
124 * Default constructor
125 *
126 * @param MembersList $filters Filtering
127 */
128 public function __construct($filters = null)
129 {
130 if ($filters === null) {
131 $this->filters = new MembersList();
132 } else {
133 $this->filters = $filters;
134 }
135 }
136
137 /**
138 * Get staff members list
139 *
140 * @param bool $as_members return the results as an array of
141 * Member object.
142 * @param array $fields field(s) name(s) to get. Should be a string or
143 * an array. If null, all fields will be
144 * returned
145 * @param boolean $count true if we want to count members
146 * @param boolean $limit true to LIMIT query
147 *
148 * @return Adherent[]|ResultSet
149 */
150 public function getStaffMembersList(
151 $as_members = false,
152 $fields = null,
153 $count = true,
154 $limit = true
155 ) {
156 return $this->getMembersList(
157 $as_members,
158 $fields,
159 $count,
160 true,
161 false,
162 $limit
163 );
164 }
165
166 /**
167 * Get managed members list (for groups managers)
168 *
169 * @param bool $as_members return the results as an array of
170 * Member object.
171 * @param array $fields field(s) name(s) to get. Should be a string or
172 * an array. If null, all fields will be
173 * returned
174 * @param boolean $count true if we want to count members
175 * @param boolean $limit true to LIMIT query
176 *
177 * @return Adherent[]|ResultSet
178 */
179 public function getManagedMembersList(
180 $as_members = false,
181 $fields = null,
182 $count = true,
183 $limit = true
184 ) {
185 return $this->getMembersList(
186 $as_members,
187 $fields,
188 $count,
189 false,
190 true,
191 $limit
192 );
193 }
194
195 /**
196 * Get members list
197 *
198 * @param bool $as_members return the results as an array of
199 * Member object.
200 * @param array $fields field(s) name(s) to get. Should be a string or
201 * an array. If null, all fields will be
202 * returned
203 * @param boolean $count true if we want to count members
204 * @param boolean $staff true if we want only staff members
205 * @param boolean $managed true if we want only managed groups
206 * @param boolean $limit true if we want records pagination
207 * @param boolean $export true if we are exporting
208 *
209 * @return Adherent[]|Laminas\Db\ResultSet
210 */
211 public function getMembersList(
212 $as_members = false,
213 $fields = null,
214 $count = true,
215 $staff = false,
216 $managed = false,
217 $limit = true,
218 $export = false
219 ) {
220 global $zdb;
221
222 if ($limit === true) {
223 //force count if limit is active
224 $count = true;
225 }
226
227 try {
228 $_mode = self::SHOW_LIST;
229 if ($staff !== false) {
230 $_mode = self::SHOW_STAFF;
231 }
232 if ($managed !== false) {
233 $_mode = self::SHOW_MANAGED;
234 }
235 if ($export !== false) {
236 $_mode = self::SHOW_EXPORT;
237 }
238
239 $select = $this->buildSelect(
240 $_mode,
241 $fields,
242 false,
243 $count
244 );
245
246 //add limits to retrieve only relavant rows
247 if ($limit === true) {
248 $this->filters->setLimits($select);
249 }
250
251 $rows = $zdb->execute($select);
252 $this->filters->query = $zdb->query_string;
253
254 $members = array();
255 if ($as_members) {
256 $deps = array(
257 'picture' => false,
258 'groups' => false
259 );
260 foreach ($rows as $row) {
261 $members[] = new Adherent($zdb, $row, $deps);
262 }
263 } else {
264 $members = $rows;
265 }
266 return $members;
267 } catch (\Exception $e) {
268 Analog::log(
269 'Cannot list members | ' . $e->getMessage(),
270 Analog::WARNING
271 );
272 throw $e;
273 }
274 }
275
276 /**
277 * Remove specified members
278 *
279 * @param integer|array $ids Members identifiers to delete
280 *
281 * @return boolean
282 */
283 public function removeMembers($ids)
284 {
285 global $zdb, $hist, $emitter;
286
287 $list = array();
288 if (is_numeric($ids)) {
289 //we've got only one identifier
290 $list[] = $ids;
291 } else {
292 $list = $ids;
293 }
294
295 if (is_array($list)) {
296 try {
297 $zdb->connection->beginTransaction();
298
299 //Retrieve some information
300 $select = $zdb->select(self::TABLE);
301 $select->columns(
302 array(self::PK, 'nom_adh', 'prenom_adh', 'email_adh')
303 )->where->in(self::PK, $list);
304
305 $results = $zdb->execute($select);
306
307 $infos = null;
308 foreach ($results as $member) {
309 $str_adh = $member->id_adh . ' (' . $member->nom_adh . ' ' .
310 $member->prenom_adh . ')';
311 $infos .= $str_adh . "\n";
312
313 $p = new Picture($member->id_adh);
314 if ($p->hasPicture()) {
315 if (!$p->delete(false)) {
316 Analog::log(
317 'Unable to delete picture for member ' . $str_adh,
318 Analog::ERROR
319 );
320 throw new \Exception(
321 'Unable to delete picture for member ' .
322 $str_adh
323 );
324 } else {
325 $hist->add(
326 _T("Member Picture deleted"),
327 $str_adh
328 );
329 }
330 }
331
332 $emitter->emit('member.remove', [
333 'id_adh' => $member->id_adh,
334 'nom_adh' => $member->nom_adh,
335 'prenom_adh' => $member->prenom_adh,
336 'email_adh' => $member->email_adh
337 ]);
338 }
339
340 //delete contributions
341 $del_qry = $zdb->delete(Contribution::TABLE);
342 $del_qry->where->in(
343 self::PK,
344 $list
345 );
346 $zdb->execute($del_qry);
347
348 //get transactions
349 $select = $zdb->select(Transaction::TABLE);
350 $select->where->in(self::PK, $list);
351 $results = $zdb->execute($select);
352
353 //if members has transactions;
354 //reset link with other contributions
355 //and remove them
356 if ($results->count() > 0) {
357 $transactions = [];
358 foreach ($results as $transaction) {
359 $transactions[] = $transaction[Transaction::PK];
360 }
361
362 $update = $zdb->update(Contribution::TABLE);
363 $update->set([
364 Transaction::PK => new Expression('NULL')
365 ])->where->in(
366 Transaction::PK,
367 $transactions
368 );
369 $zdb->execute($update);
370 }
371
372 //delete transactions
373 $del_qry = $zdb->delete(Transaction::TABLE);
374 $del_qry->where->in(self::PK, $list);
375 $zdb->execute($del_qry);
376
377 //delete groups membership/mamagmentship
378 Groups::removeMembersFromGroups($list);
379
380 //delete reminders
381 $del_qry = $zdb->delete(Reminder::TABLE);
382 $del_qry->where->in(
383 'reminder_dest',
384 $list
385 );
386 $zdb->execute($del_qry);
387
388 //delete dynamic fields values
389 $del_qry = $zdb->delete(DynamicFieldsHandle::TABLE);
390 $del_qry->where(['field_form' => 'adh']);
391 $del_qry->where->in('item_id', $list);
392 $zdb->execute($del_qry);
393
394 //delete members
395 $del_qry = $zdb->delete(self::TABLE);
396 $del_qry->where->in(
397 self::PK,
398 $list
399 );
400 $zdb->execute($del_qry);
401
402 //commit all changes
403 $zdb->connection->commit();
404
405 //add an history entry
406 $hist->add(
407 _T("Delete members cards, transactions and dues"),
408 $infos
409 );
410
411 return true;
412 } catch (\Exception $e) {
413 $zdb->connection->rollBack();
414 if ($e->getCode() == 23000) {
415 Analog::log(
416 'Member still have existing dependencies in the ' .
417 'database, maybe a mailing or some content from a ' .
418 'plugin. Please remove dependencies before trying ' .
419 'to remove him.',
420 Analog::ERROR
421 );
422 $this->errors[] = _T("Cannot remove a member who still have dependencies (mailings, ...)");
423 } else {
424 Analog::log(
425 'Unable to delete selected member(s) |' .
426 $e->getMessage(),
427 Analog::ERROR
428 );
429 }
430 return false;
431 }
432 } else {
433 //not numeric and not an array: incorrect.
434 Analog::log(
435 'Asking to remove members, but without providing an array or a single numeric value.',
436 Analog::ERROR
437 );
438 return false;
439 }
440 }
441
442 /**
443 * Get members list
444 *
445 * @param boolean $as_members return the results as an array of
446 * Member object.
447 * @param array $fields field(s) name(s) to get. Should be a string or
448 * an array. If null, all fields will be
449 * returned
450 *
451 * @return Adherent[]|ResultSet
452 */
453 public function getList($as_members = false, $fields = null)
454 {
455 return $this->getMembersList(
456 $as_members,
457 $fields,
458 false,
459 false,
460 false,
461 true,
462 false
463 );
464 }
465
466 /**
467 * Get members list with public information available
468 *
469 * @param boolean $with_photos get only members which have uploaded a
470 * photo (for trombinoscope)
471 *
472 * @return Adherent[]
473 */
474 public function getPublicList($with_photos)
475 {
476 global $zdb;
477
478 try {
479 $select = $this->buildSelect(
480 self::SHOW_PUBLIC_LIST,
481 null,
482 $with_photos,
483 true
484 );
485
486 $this->filters->setLimits($select);
487
488 $results = $zdb->execute($select);
489 $members = array();
490 foreach ($results as $row) {
491 $deps = array(
492 'groups' => false,
493 'dues' => false,
494 'picture' => $with_photos
495 );
496 $members[] = new Adherent($zdb, $row, $deps);
497 }
498 return $members;
499 } catch (\Exception $e) {
500 Analog::log(
501 'Cannot list members with public information (photos: '
502 . $with_photos . ') | ' . $e->getMessage(),
503 Analog::WARNING
504 );
505 return false;
506 }
507 }
508
509 /**
510 * Get list of members that has been selected
511 *
512 * @param array $ids an array of members id that has been selected
513 * @param array $orderby SQL order clause (optionnal)
514 * @param boolean $with_photos Should photos be loaded?
515 * @param boolean $as_members Return Adherent[] or simple ResultSet
516 * @param array $fields Fields to use
517 * @param boolean $export True if we are exporting
518 * @param boolean $dues True if load dues as Adherent dependency
519 * @param boolean $parent True if load parent as Adherent dependency
520 *
521 * @return Adherent[]
522 */
523 public function getArrayList(
524 $ids,
525 $orderby = null,
526 $with_photos = false,
527 $as_members = true,
528 $fields = null,
529 $export = false,
530 $dues = false,
531 $parent = false
532 ) {
533 global $zdb;
534
535 if (!is_array($ids) || count($ids) < 1) {
536 Analog::log('No member selected for labels.', Analog::INFO);
537 return false;
538 }
539
540 try {
541 $damode = self::SHOW_ARRAY_LIST;
542 if ($export === true) {
543 $damode = self::SHOW_EXPORT;
544 }
545 $select = $this->buildSelect(
546 $damode,
547 $fields,
548 false,
549 false
550 );
551 $select->where->in('a.' . self::PK, $ids);
552 if (is_array($orderby) && count($orderby) > 0) {
553 foreach ($orderby as $o) {
554 $select->order($o);
555 }
556 }
557
558 $results = $zdb->execute($select);
559
560 $members = array();
561 foreach ($results as $o) {
562 $deps = array(
563 'picture' => $with_photos,
564 'groups' => false,
565 'dues' => $dues,
566 'parent' => $parent
567 );
568 if ($as_members === true) {
569 $members[] = new Adherent($zdb, $o, $deps);
570 } else {
571 $members[] = $o;
572 }
573 }
574 return $members;
575 } catch (\Exception $e) {
576 Analog::log(
577 'Cannot load members form ids array | ' . $e->getMessage(),
578 Analog::WARNING
579 );
580 }
581 }
582
583 /**
584 * Builds the SELECT statement
585 *
586 * @param int $mode the current mode (see self::SHOW_*)
587 * @param array $fields fields list to retrieve
588 * @param bool $photos true if we want to get only members with photos
589 * Default to false, only relevant for SHOW_PUBLIC_LIST
590 * @param bool $count true if we want to count members, defaults to false
591 *
592 * @return Select SELECT statement
593 */
594 private function buildSelect($mode, $fields, $photos, $count = false): Select
595 {
596 global $zdb, $login;
597
598 try {
599 if ($fields != null && is_array($fields) && !in_array('id_adh', $fields)) {
600 $fields[] = 'id_adh';
601 }
602 $fieldsList = ($fields != null)
603 ? ((!is_array($fields) || count($fields) < 1) ? (array)'*'
604 : $fields) : (array)'*';
605
606 $select = $zdb->select(self::TABLE, 'a');
607
608 $select->columns($fieldsList);
609
610 $select->quantifier('DISTINCT');
611
612 switch ($mode) {
613 case self::SHOW_STAFF:
614 case self::SHOW_LIST:
615 case self::SHOW_ARRAY_LIST:
616 $select->join(
617 array('p' => PREFIX_DB . Status::TABLE),
618 'a.' . Status::PK . '=p.' . Status::PK,
619 array()
620 );
621 break;
622 case self::SHOW_EXPORT:
623 //basically the same as above, but without any fields
624 $select->join(
625 array('p' => PREFIX_DB . Status::TABLE),
626 'a.' . Status::PK . '=p.' . Status::PK,
627 array()
628 );
629 break;
630 case self::SHOW_MANAGED:
631 $select->join(
632 array('p' => PREFIX_DB . Status::TABLE),
633 'a.' . Status::PK . '=p.' . Status::PK
634 )->join(
635 array('gr' => PREFIX_DB . Group::GROUPSUSERS_TABLE),
636 'a.' . Adherent::PK . '=gr.' . Adherent::PK,
637 array()
638 )->join(
639 array('m' => PREFIX_DB . Group::GROUPSMANAGERS_TABLE),
640 'gr.' . Group::PK . '=m.' . Group::PK,
641 array()
642 )->where('m.' . Adherent::PK . ' = ' . $login->id);
643 break;
644 case self::SHOW_PUBLIC_LIST:
645 if ($photos) {
646 $select->join(
647 array('p' => PREFIX_DB . Picture::TABLE),
648 'a.' . self::PK . '= p.' . self::PK,
649 array()
650 );
651 }
652 break;
653 }
654
655 //check for contributions filtering
656 if (
657 $this->filters instanceof AdvancedMembersList
658 && $this->filters->withinContributions()
659 ) {
660 $select->join(
661 array('ct' => PREFIX_DB . Contribution::TABLE),
662 'ct.' . self::PK . '=a.' . self::PK,
663 array(),
664 $select::JOIN_LEFT
665 );
666 }
667
668 //check if there are dynamic fields in filter
669 $hasDf = false;
670 $dfs = array();
671
672 if (
673 $this->filters instanceof AdvancedMembersList
674 && $this->filters->free_search
675 && count($this->filters->free_search) > 0
676 && !isset($this->filters->free_search['empty'])
677 ) {
678 $free_searches = $this->filters->free_search;
679 foreach ($free_searches as $fs) {
680 if (strpos($fs['field'], 'dyn_') === 0) {
681 // simple dynamic fields
682 $hasDf = true;
683 $dfs[] = str_replace('dyn_', '', $fs['field']);
684 }
685 }
686 }
687
688 //check if there are dynamic fields for contributions in filter
689 $hasDfc = false;
690 $hasCdfc = false;
691 $cdfcs = array();
692
693 if (
694 $this->filters instanceof AdvancedMembersList
695 && $this->filters->withinContributions()
696 ) {
697 if (
698 $this->filters->contrib_dynamic
699 && count($this->filters->contrib_dynamic) > 0
700 && !isset($this->filters->contrib_dynamic['empty'])
701 ) {
702 $hasDfc = true;
703
704 //check if there are dynamic fields in the filter
705 foreach ($this->filters->contrib_dynamic as $k => $cd) {
706 if (is_array($cd)) {
707 $hasCdfc = true;
708 $cdfcs[] = $k;
709 }
710 }
711 }
712 }
713
714 if ($hasDfc === true || $hasCdfc === true) {
715 $select->join(
716 array('dfc' => PREFIX_DB . DynamicFieldsHandle::TABLE),
717 'dfc.item_id=ct.' . Contribution::PK,
718 array(),
719 $select::JOIN_LEFT
720 );
721 }
722
723 // simple dynamic fields
724 if ($hasDf === true) {
725 foreach ($dfs as $df) {
726 $subselect = $zdb->select(DynamicFieldsHandle::TABLE, 'df');
727 $subselect->columns(
728 [
729 'item_id' => 'item_id',
730 'val' => 'field_val'
731 ]
732 );
733 $subselect->where('df.field_form = \'adh\'');
734 $subselect->where('df.field_id = ' . $df);
735 $select->join(
736 array('df' . $df => $subselect),
737 'a.id_adh = df' . $df . '.item_id',
738 array(),
739 $select::JOIN_LEFT
740 );
741 }
742 }
743
744 // choice dynamic fields
745 if ($hasCdfc === true) {
746 $cdf_field = 'cdf.id';
747 if (TYPE_DB === 'pgsql') {
748 $cdf_field .= '::text';
749 }
750
751 $cdf_field = 'cdfc.id';
752 if (TYPE_DB === 'pgsql') {
753 $cdf_field .= '::text';
754 }
755 foreach ($cdfcs as $cdf) {
756 $rcdf_field = str_replace(
757 'cdfc.',
758 'cdfc' . $cdf . '.',
759 $cdf_field
760 );
761 $select->join(
762 array('cdfc' . $cdf => DynamicField::getFixedValuesTableName($cdf, true)),
763 $rcdf_field . '=dfc.field_val',
764 array(),
765 $select::JOIN_LEFT
766 );
767 }
768 }
769
770 if ($mode == self::SHOW_LIST || $mode == self::SHOW_MANAGED) {
771 if ($this->filters !== false) {
772 $this->buildWhereClause($select);
773 }
774 } elseif ($mode == self::SHOW_PUBLIC_LIST) {
775 $select->where(
776 array(
777 new PredicateSet(
778 array(
779 new Operator(
780 'date_echeance',
781 '>=',
782 date('Y-m-d')
783 ),
784 new Operator(
785 'bool_exempt_adh',
786 '=',
787 new Expression('true')
788 )
789 ),
790 PredicateSet::OP_OR
791 ),
792 new PredicateSet(
793 array(
794 new Operator(
795 'bool_display_info',
796 '=',
797 new Expression('true')
798 ),
799 new Operator(
800 'activite_adh',
801 '=',
802 new Expression('true')
803 )
804 ),
805 PredicateSet::OP_AND
806 )
807 )
808 );
809 }
810
811 if ($mode === self::SHOW_STAFF) {
812 $select->where->lessThan(
813 'p.priorite_statut',
814 self::NON_STAFF_MEMBERS
815 );
816 }
817
818 if ($count) {
819 $this->proceedCount($select);
820 }
821
822 $this->buildOrderClause($select, $fields);
823
824 return $select;
825 } catch (\Exception $e) {
826 Analog::log(
827 'Cannot build SELECT clause for members | ' . $e->getMessage(),
828 Analog::WARNING
829 );
830 throw $e;
831 }
832 }
833
834 /**
835 * Count members from the query
836 *
837 * @param Select $select Original select
838 *
839 * @return void
840 */
841 private function proceedCount(Select $select)
842 {
843 global $zdb;
844
845 try {
846 $countSelect = clone $select;
847 $countSelect->reset($countSelect::COLUMNS);
848 $countSelect->reset($countSelect::ORDER);
849 $countSelect->reset($countSelect::HAVING);
850 $joins = $countSelect->joins;
851 $countSelect->reset($countSelect::JOINS);
852 foreach ($joins as $join) {
853 $countSelect->join(
854 $join['name'],
855 $join['on'],
856 [],
857 $join['type']
858 );
859 unset($join['columns']);
860 }
861 $countSelect->columns(
862 array(
863 'count' => new Expression('count(DISTINCT a.' . self::PK . ')')
864 )
865 );
866
867 $have = $select->having;
868 if ($have->count() > 0) {
869 foreach ($have->getPredicates() as $h) {
870 $countSelect->where($h);
871 }
872 }
873
874 $results = $zdb->execute($countSelect);
875
876 $this->count = (int)$results->current()->count;
877 if (isset($this->filters) && $this->count > 0) {
878 $this->filters->setCounter($this->count);
879 }
880 } catch (\Exception $e) {
881 Analog::log(
882 'Cannot count members | ' . $e->getMessage(),
883 Analog::WARNING
884 );
885 return false;
886 }
887 }
888
889 /**
890 * Builds the order clause
891 *
892 * @param Select $select Original select
893 * @param array $fields Fields list to ensure ORDER clause
894 * references selected fields. Optionnal.
895 *
896 * @return Select
897 */
898 private function buildOrderClause(Select $select, $fields = null): Select
899 {
900 $order = array();
901
902 switch ($this->filters->orderby) {
903 case self::ORDERBY_NICKNAME:
904 if ($this->canOrderBy('pseudo_adh', $fields)) {
905 $order[] = 'pseudo_adh ' . $this->filters->getDirection();
906 }
907 break;
908 case self::ORDERBY_STATUS:
909 if ($this->canOrderBy('priorite_statut', $fields)) {
910 $order[] = 'priorite_statut ' . $this->filters->getDirection();
911 }
912 break;
913 case self::ORDERBY_MODIFDATE:
914 if ($this->canOrderBy('date_modif_adh', $fields)) {
915 $order[] = 'date_modif_adh ' . $this->filters->getDirection();
916 }
917 break;
918 case 'list_adh_contribstatus':
919 case self::ORDERBY_FEE_STATUS:
920 if ($this->canOrderBy('bool_exempt_adh', $fields)) {
921 $order[] = 'bool_exempt_adh ' . $this->filters->getDirection();
922 }
923
924 if ($this->canOrderBy('date_echeance', $fields)) {
925 $order[] = 'date_echeance ' . $this->filters->getDirection();
926 }
927 break;
928 case self::ORDERBY_ID:
929 if ($this->canOrderBy('id_adh', $fields)) {
930 $order[] = 'id_adh ' . $this->filters->getDirection();
931 }
932 break;
933 case 'list_adh_name':
934 case 'nom_adh':
935 case 'prenom_adh':
936 //defaults
937 break;
938 default:
939 if ($this->canOrderBy($this->filters->orderby, $fields)) {
940 $order[] = $this->filters->orderby . ' ' . $this->filters->getDirection();
941 }
942 break;
943 }
944
945 //anyways, we want to order by firstname, lastname
946 if ($this->canOrderBy('nom_adh', $fields)) {
947 $order[] = 'nom_adh ' . $this->filters->getDirection();
948 }
949 if ($this->canOrderBy('prenom_adh', $fields)) {
950 $order[] = 'prenom_adh ' . $this->filters->getDirection();
951 }
952
953 $select->order($order);
954 return $select;
955 }
956
957 /**
958 * Is field allowed to order? it shoulsd be present in
959 * provided fields list (those that are SELECT'ed).
960 *
961 * @param string $field_name Field name to order by
962 * @param array $fields SELECTE'ed fields
963 *
964 * @return boolean
965 */
966 private function canOrderBy($field_name, $fields)
967 {
968 if ($fields === null) {
969 return true;
970 } elseif (!is_array($fields)) {
971 return false;
972 } elseif (in_array($field_name, $fields)) {
973 return true;
974 } else {
975 Analog::log(
976 'Trying to order by ' . $field_name . ' while it is not in ' .
977 'selected fields.',
978 Analog::WARNING
979 );
980 return false;
981 }
982 }
983
984 /**
985 * Builds where clause, for filtering on simple list mode
986 *
987 * @param Select $select Original select
988 *
989 * @return void
990 */
991 private function buildWhereClause(Select $select)
992 {
993 global $zdb, $login;
994
995 try {
996 if ($this->filters->email_filter == self::FILTER_W_EMAIL) {
997 $select->where('email_adh != \'\'');
998 }
999 if ($this->filters->email_filter == self::FILTER_WO_EMAIL) {
1000 $select->where('(email_adh = \'\' OR email_adh IS NULL)');
1001 }
1002
1003 if ($this->filters->filter_str != '') {
1004 $token = $zdb->platform->quoteValue(
1005 '%' . strtolower($this->filters->filter_str) . '%'
1006 );
1007 switch ($this->filters->field_filter) {
1008 case self::FILTER_NAME:
1009 if (TYPE_DB === 'pgsql') {
1010 $sep = " || ' ' || ";
1011 $pre = '';
1012 $post = '';
1013 } else {
1014 $sep = ', " ", ';
1015 $pre = 'CONCAT(';
1016 $post = ')';
1017 }
1018
1019 $select->where(
1020 '(' .
1021 $pre . 'LOWER(nom_adh)' . $sep .
1022 'LOWER(prenom_adh)' . $sep .
1023 'LOWER(pseudo_adh)' . $post . ' LIKE ' .
1024 $token
1025 . ' OR ' .
1026 $pre . 'LOWER(prenom_adh)' . $sep .
1027 'LOWER(nom_adh)' . $sep .
1028 'LOWER(pseudo_adh)' . $post . ' LIKE ' .
1029 $token
1030 . ')'
1031 );
1032 break;
1033 case self::FILTER_COMPANY_NAME:
1034 $select->where(
1035 'LOWER(societe_adh) LIKE ' .
1036 $token
1037 );
1038 break;
1039 case self::FILTER_ADDRESS:
1040 $select->where(
1041 '(' .
1042 'LOWER(adresse_adh) LIKE ' . $token
1043 . ' OR ' .
1044 'LOWER(adresse2_adh) LIKE ' . $token
1045 . ' OR ' .
1046 'cp_adh LIKE ' . $token
1047 . ' OR ' .
1048 'LOWER(ville_adh) LIKE ' . $token
1049 . ' OR ' .
1050 'LOWER(pays_adh) LIKE ' . $token
1051 . ')'
1052 );
1053 break;
1054 case self::FILTER_MAIL:
1055 $select->where(
1056 '(' .
1057 'LOWER(email_adh) LIKE ' . $token
1058 . ' OR ' .
1059 'LOWER(url_adh) LIKE ' . $token
1060 . ' OR ' .
1061 'LOWER(msn_adh) LIKE ' . $token
1062 . ' OR ' .
1063 'LOWER(icq_adh) LIKE ' . $token
1064 . ' OR ' .
1065 'LOWER(jabber_adh) LIKE ' . $token
1066 . ')'
1067 );
1068 break;
1069 case self::FILTER_JOB:
1070 $select->where(
1071 'LOWER(prof_adh) LIKE ' . $token
1072 );
1073 break;
1074 case self::FILTER_INFOS:
1075 $more = '';
1076 if ($login->isAdmin() || $login->isStaff()) {
1077 $more = ' OR LOWER(info_adh) LIKE ' . $token;
1078 }
1079 $select->where(
1080 '(LOWER(info_public_adh) LIKE ' .
1081 $token . $more . ')'
1082 );
1083 break;
1084 case self::FILTER_NUMBER:
1085 $select->where->equalTo('a.id_adh', $this->filters->filter_str);
1086 break;
1087 }
1088 }
1089
1090 if ($this->filters->membership_filter) {
1091 switch ($this->filters->membership_filter) {
1092 case self::MEMBERSHIP_NEARLY:
1093 $now = new \DateTime();
1094 $duedate = new \DateTime();
1095 $duedate->modify('+1 month');
1096 $select->where->greaterThanOrEqualTo(
1097 'date_echeance',
1098 $now->format('Y-m-d')
1099 )->lessThan(
1100 'date_echeance',
1101 $duedate->format('Y-m-d')
1102 );
1103 break;
1104 case self::MEMBERSHIP_LATE:
1105 $select->where
1106 ->lessThan(
1107 'date_echeance',
1108 date('Y-m-d', time())
1109 )->equalTo('bool_exempt_adh', new Expression('false'));
1110 break;
1111 case self::MEMBERSHIP_UP2DATE:
1112 $select->where(
1113 '(' . 'date_echeance >= \'' . date('Y-m-d', time())
1114 . '\' OR bool_exempt_adh=true)'
1115 );
1116 break;
1117 case self::MEMBERSHIP_NEVER:
1118 $select->where('date_echeance IS NULL')
1119 ->where('bool_exempt_adh = false');
1120 break;
1121 case self::MEMBERSHIP_STAFF:
1122 $select->where->lessThan(
1123 'p.priorite_statut',
1124 self::NON_STAFF_MEMBERS
1125 );
1126 break;
1127 case self::MEMBERSHIP_ADMIN:
1128 $select->where->equalTo('bool_admin_adh', true);
1129 break;
1130 case self::MEMBERSHIP_NONE:
1131 $select->where->equalTo('a.id_statut', Status::DEFAULT_STATUS);
1132 break;
1133 }
1134 }
1135
1136 if ($this->filters->filter_account) {
1137 switch ($this->filters->filter_account) {
1138 case self::ACTIVE_ACCOUNT:
1139 $select->where('activite_adh=true');
1140 break;
1141 case self::INACTIVE_ACCOUNT:
1142 $select->where('activite_adh=false');
1143 break;
1144 }
1145 }
1146
1147 if ($this->filters->group_filter) {
1148 $select->join(
1149 array('g' => PREFIX_DB . Group::GROUPSUSERS_TABLE),
1150 'a.' . Adherent::PK . '=g.' . Adherent::PK,
1151 array(),
1152 $select::JOIN_LEFT
1153 )->join(
1154 array('gs' => PREFIX_DB . Group::TABLE),
1155 'gs.' . Group::PK . '=g.' . Group::PK,
1156 array(),
1157 $select::JOIN_LEFT
1158 )->where(
1159 '(g.' . Group::PK . ' = ' . $this->filters->group_filter .
1160 ' OR gs.parent_group = NULL OR gs.parent_group = ' .
1161 $this->filters->group_filter . ')'
1162 );
1163 }
1164
1165 if ($this->filters instanceof AdvancedMembersList) {
1166 $this->buildAdvancedWhereClause($select);
1167 }
1168
1169 return $select;
1170 } catch (\Exception $e) {
1171 Analog::log(
1172 __METHOD__ . ' | ' . $e->getMessage(),
1173 Analog::WARNING
1174 );
1175 throw $e;
1176 }
1177 }
1178
1179 /**
1180 * Builds where clause, for advanced filtering on simple list mode
1181 *
1182 * @param Select $select Original select
1183 *
1184 * @return void
1185 */
1186 private function buildAdvancedWhereClause(Select $select)
1187 {
1188 global $zdb, $login;
1189
1190 // Search members who belong to any (OR) or all (AND) listed groups.
1191 // Idea is to build an array of members ID that fits groups selection
1192 // we will use in the final query.
1193 // The OR case is quite simple, AND is a bit more complex; since we must
1194 // check each member do belongs to all listed groups.
1195 if (
1196 count($this->filters->groups_search) > 0
1197 && !isset($this->filters->groups_search['empty'])
1198 ) {
1199 $groups_adh = [];
1200 $wheregroups = [];
1201
1202 foreach ($this->filters->groups_search as $gs) { // then add a row for each group
1203 $wheregroups[] = $gs['group'];
1204 }
1205
1206 $gselect = $zdb->select(Group::GROUPSUSERS_TABLE, 'gu');
1207 $gselect->columns(
1208 array('id_adh')
1209 )->join(
1210 array('g' => PREFIX_DB . Group::TABLE),
1211 'gu.id_group=g.' . Group::PK,
1212 array(),
1213 $select::JOIN_LEFT
1214 )->where(
1215 array(
1216 'g.id_group' => ':group',
1217 'g.parent_group' => ':pgroup'
1218 ),
1219 PredicateSet::OP_OR
1220 );
1221 $gselect->group(['gu.id_adh']);
1222
1223 $stmt = $zdb->sql->prepareStatementForSqlObject($gselect);
1224
1225 $mids = [];
1226 $ids = [];
1227 foreach ($this->filters->groups_search as $gs) { // then add a row for each ig/searched group pair
1228 /** Why where parameter is named where1 ?? */
1229 $gresults = $stmt->execute(
1230 array(
1231 'where1' => $gs['group'],
1232 'where2' => $gs['group']
1233 )
1234 );
1235
1236 switch ($this->filters->groups_search_log_op) {
1237 case AdvancedMembersList::OP_AND:
1238 foreach ($gresults as $gresult) {
1239 if (!isset($ids[$gresult['id_adh']])) {
1240 $ids[$gresult['id_adh']] = 0;
1241 }
1242 $ids[$gresult['id_adh']] += 1;
1243 }
1244 break;
1245 case AdvancedMembersList::OP_OR:
1246 foreach ($gresults as $gresult) {
1247 $mids[$gresult['id_adh']] = $gresult['id_adh'];
1248 }
1249 break;
1250 }
1251 }
1252
1253 if (count($ids)) {
1254 foreach ($ids as $id_adh => $count) {
1255 if ($count == count($wheregroups)) {
1256 $mids[$id_adh] = $id_adh;
1257 }
1258 }
1259 }
1260
1261 if (count($mids)) {
1262 //limit on found members
1263 $select->where->in('a.id_adh', $mids);
1264 } else {
1265 //no match in groups, end of game.
1266 $select->where('false = true');
1267 }
1268 }
1269
1270 //FIXME: should be retrieved from members_fields
1271 $dates = [
1272 'ddn_adh' => 'birth_date',
1273 'date_crea_adh' => 'creation_date',
1274 'date_modif_adh' => 'modif_date',
1275 'date_echeance' => 'due_date',
1276 'ct.date_enreg' => 'contrib_creation_date',
1277 'ct.date_debut_cotis' => 'contrib_begin_date',
1278 'ct.date_fin_cotis' => 'contrib_end_date'
1279 ];
1280
1281 foreach ($dates as $field => $property) {
1282 $bprop = "r{$property}_begin";
1283 if ($this->filters->$bprop) {
1284 $d = new \DateTime($this->filters->$bprop);
1285 $select->where->greaterThanOrEqualTo(
1286 $field,
1287 $d->format('Y-m-d')
1288 );
1289 }
1290 $eprop = "r{$property}_end";
1291 if ($this->filters->$eprop) {
1292 $d = new \DateTime($this->filters->$eprop);
1293 $select->where->lessThanOrEqualTo(
1294 $field,
1295 $d->format('Y-m-d')
1296 );
1297 }
1298 }
1299
1300 if ($this->filters->show_public_infos) {
1301 switch ($this->filters->show_public_infos) {
1302 case self::FILTER_W_PUBINFOS:
1303 $select->where('bool_display_info = true');
1304 break;
1305 case self::FILTER_WO_PUBINFOS:
1306 $select->where('bool_display_info = false');
1307 break;
1308 case self::FILTER_DC_PUBINFOS:
1309 //nothing to do here.
1310 break;
1311 }
1312 }
1313
1314 if ($this->filters->status) {
1315 $select->where->in(
1316 'a.id_statut',
1317 $this->filters->status
1318 );
1319 }
1320
1321 if (
1322 $this->filters->contrib_min_amount
1323 || $this->filters->contrib_max_amount
1324 ) {
1325 if ($this->filters->contrib_min_amount) {
1326 $select->where->greaterThanOrEqualTo(
1327 'ct.montant_cotis',
1328 $this->filters->contrib_min_amount
1329 );
1330 }
1331 if ($this->filters->contrib_max_amount) {
1332 $select->where->lessThanOrEqualTo(
1333 'ct.montant_cotis',
1334 $this->filters->contrib_max_amount
1335 );
1336 }
1337 }
1338
1339 if ($this->filters->contributions_types) {
1340 $select->where->in(
1341 'ct.id_type_cotis',
1342 $this->filters->contributions_types
1343 );
1344 }
1345
1346 if ($this->filters->payments_types) {
1347 $select->where->in(
1348 'ct.type_paiement_cotis',
1349 $this->filters->payments_types
1350 );
1351 }
1352
1353 if (
1354 count($this->filters->contrib_dynamic) > 0
1355 && !isset($this->filters->contrib_dynamic['empty'])
1356 ) {
1357 foreach ($this->filters->contrib_dynamic as $k => $cd) {
1358 $qry = '';
1359 $prefix = 'a.';
1360 $field = null;
1361 $qop = ' LIKE ';
1362
1363 if (is_array($cd)) {
1364 //dynamic choice spotted!
1365 $prefix = 'cdfc' . $k . '.';
1366 $qry = 'dfc.field_form = \'contrib\' AND ' .
1367 'dfc.field_id = ' . $k . ' AND ';
1368 $field = 'id';
1369 $select->where->in($prefix . $field, $cd);
1370 } else {
1371 //dynamic field spotted!
1372 $prefix = 'dfc.';
1373 $qry = 'dfc.field_form = \'contrib\' AND ' .
1374 'dfc.field_id = ' . $k . ' AND ';
1375 $field = 'field_val';
1376 $qry .= 'LOWER(' . $prefix . $field . ') ' .
1377 $qop . ' ';
1378 $select->where($qry . $zdb->platform->quoteValue('%' . strtolower($cd) . '%'));
1379 }
1380 }
1381 }
1382
1383 if (
1384 count($this->filters->free_search) > 0
1385 && !isset($this->filters->free_search['empty'])
1386 ) {
1387 foreach ($this->filters->free_search as $fs) {
1388 $fs['search'] = mb_strtolower($fs['search']);
1389 $qop = null;
1390 switch ($fs['qry_op']) {
1391 case AdvancedMembersList::OP_EQUALS:
1392 $qop = '=';
1393 break;
1394 case AdvancedMembersList::OP_CONTAINS:
1395 $qop = 'LIKE';
1396 $fs['search'] = '%' . $fs['search'] . '%';
1397 break;
1398 case AdvancedMembersList::OP_NOT_EQUALS:
1399 $qop = '!=';
1400 break;
1401 case AdvancedMembersList::OP_NOT_CONTAINS:
1402 $qop = 'NOT LIKE';
1403 $fs['search'] = '%' . $fs['search'] . '%';
1404 break;
1405 case AdvancedMembersList::OP_STARTS_WITH:
1406 $qop = 'LIKE';
1407 $fs['search'] = $fs['search'] . '%';
1408 break;
1409 case AdvancedMembersList::OP_ENDS_WITH:
1410 $qop = 'LIKE';
1411 $fs['search'] = '%' . $fs['search'];
1412 break;
1413 case AdvancedMembersList::OP_BEFORE:
1414 $qop = '<';
1415 break;
1416 case AdvancedMembersList::OP_AFTER:
1417 $qop = '>';
1418 break;
1419 default:
1420 Analog::log(
1421 'Unknown query operator: ' . $fs['qry_op'] .
1422 ' (will fallback to equals)',
1423 Analog::WARNING
1424 );
1425 $qop = '=';
1426 break;
1427 }
1428
1429 $qry = '';
1430 $prefix = 'a.';
1431 $dyn_field = false;
1432 if (strpos($fs['field'], 'dyn_') === 0) {
1433 // simple dynamic field spotted!
1434 $index = str_replace('dyn_', '', $fs['field']);
1435 $dyn_field = DynamicField::loadFieldType($zdb, (int)$index);
1436 $prefix = 'df' . $index . '.';
1437 $fs['field'] = 'val';
1438 }
1439
1440 if ($dyn_field && $dyn_field instanceof \Galette\DynamicFields\Boolean) {
1441 if ($fs['search'] != 0) {
1442 $qry .= $prefix . $fs['field'] . $qop . ' ' .
1443 $fs['search'];
1444 } else {
1445 $qry .= $prefix . $fs['field'] . ' IS NULL';
1446 }
1447 } elseif (!strncmp($fs['field'], 'bool_', strlen('bool_'))) {
1448 $qry .= $prefix . $fs['field'] . $qop . ' ' .
1449 $fs['search'];
1450 } elseif (
1451 $fs['qry_op'] === AdvancedMembersList::OP_BEFORE
1452 || $fs['qry_op'] === AdvancedMembersList::OP_AFTER
1453 ) {
1454 if ($prefix === 'a.') {
1455 //dates are OK in the main fields. no cast, just query!
1456 $qry .= $prefix . $fs['field'] . $qop . ' ' .
1457 $zdb->platform->quoteValue($fs['search']);
1458 } else {
1459 //dynamic dates are stored in their localized format :/
1460 //use current lang format to query for now
1461 if ($zdb->isPostgres()) {
1462 $fs['search'] = "to_date('" . $fs['search'] . "', 'YYYY-MM-DD')";
1463 $store_fmt = __("Y-m-d") === 'Y-m-d' ? 'YYYY-MM-DD' : 'DD/MM/YYYY';
1464 $qry .= "to_date('" . $prefix . $fs['field'] . "', '$store_fmt')";
1465 } else {
1466 $fs['search'] = "STR_TO_DATE('" . $fs['search'] . "', '%Y-%m-%d')";
1467 $store_fmt = __("Y-m-d") === 'Y-m-d' ? '%Y-%m-%d' : '%d/%m/%Y';
1468 $qry .= 'STR_TO_DATE(' . $prefix . $fs['field'] . ', \'' . $store_fmt . '\') ';
1469 }
1470
1471 $qry .= $qop . ' ' . $fs['search'];
1472 }
1473 } elseif ($fs['field'] == 'status_label') {
1474 $qry_pattern = '%p%field %op %value';
1475 $qry .= str_replace(
1476 [
1477 '%p',
1478 '%field',
1479 '%op',
1480 '%value'
1481 ],
1482 [
1483 'p.',
1484 'libelle_statut',
1485 $qop,
1486 $zdb->platform->quoteValue($fs['search'])
1487 ],
1488 $qry_pattern
1489 );
1490 } else {
1491 $qry .= 'LOWER(' . $prefix . $fs['field'] . ') ' .
1492 $qop . ' ' . $zdb->platform->quoteValue($fs['search']);
1493 }
1494
1495 if ($fs['log_op'] === AdvancedMembersList::OP_AND) {
1496 $select->where($qry);
1497 } elseif ($fs['log_op'] === AdvancedMembersList::OP_OR) {
1498 $select->where($qry, PredicateSet::OP_OR);
1499 }
1500 }
1501 }
1502
1503 return $select;
1504 }
1505
1506 /**
1507 * Login and password field cannot be empty.
1508 *
1509 * If those ones are not required, or if a file has been imported
1510 * (from a CSV file for example), we fill here random values.
1511 *
1512 * @return boolean
1513 */
1514 public function emptyLogins()
1515 {
1516 global $zdb;
1517
1518 try {
1519 $zdb->connection->beginTransaction();
1520 $select = $zdb->select(Adherent::TABLE);
1521 $select->columns(
1522 array('id_adh', 'login_adh', 'mdp_adh')
1523 )->where(
1524 array(
1525 'login_adh' => new Expression('NULL'),
1526 'login_adh' => '',
1527 'mdp_adh' => new Expression('NULL'),
1528 'mdp_adh' => ''
1529 ),
1530 PredicateSet::OP_OR
1531 );
1532
1533 $results = $zdb->execute($select);
1534
1535 $processed = 0;
1536 if ($results->count() > 0) {
1537 $update = $zdb->update(Adherent::TABLE);
1538 $update->set(
1539 array(
1540 'login_adh' => ':login',
1541 'mdp_adh' => ':pass'
1542 )
1543 )->where->equalTo(Adherent::PK, ':id');
1544
1545 $stmt = $zdb->sql->prepareStatementForSqlObject($update);
1546
1547 $p = new \Galette\Core\Password($zdb);
1548
1549 foreach ($results as $m) {
1550 $dirty = false;
1551 if (
1552 $m->login_adh == ''
1553 || !isset($m->login_adh)
1554 || $m->login_adh == 'NULL'
1555 ) {
1556 $m->login_adh = $p->makeRandomPassword(15);
1557 $dirty = true;
1558 }
1559
1560 if (
1561 $m->mdp_adh == ''
1562 || !isset($m->mdp_adh)
1563 || $m->mdp_adh == 'NULL'
1564 ) {
1565 $randomp = $p->makeRandomPassword(15);
1566 $m->mdp_adh = password_hash(
1567 $randomp,
1568 PASSWORD_BCRYPT
1569 );
1570 $dirty = true;
1571 }
1572
1573 if ($dirty === true) {
1574 /** Why where parameter is named where1 ?? */
1575 $stmt->execute(
1576 array(
1577 'login_adh' => $m->login_adh,
1578 'mdp_adh' => $m->mdp_adh,
1579 'where1' => $m->id_adh
1580 )
1581 );
1582 $processed++;
1583 }
1584 }
1585 }
1586 $zdb->connection->commit();
1587 $this->count = $processed;
1588 return true;
1589 } catch (\Exception $e) {
1590 $zdb->connection->rollBack();
1591 Analog::log(
1592 'An error occurred trying to retrieve members with ' .
1593 'empty logins/passwords (' . $e->getMessage(),
1594 Analog::ERROR
1595 );
1596 return false;
1597 }
1598 }
1599
1600 /**
1601 * Loads data to produce a Pie chart based on members state of dues
1602 *
1603 * @return void
1604 */
1605 public function getRemindersCount()
1606 {
1607 global $zdb;
1608
1609 $reminders = array();
1610
1611 $soon_date = new \DateTime();
1612 $soon_date->modify('+1 month');
1613
1614 $now = new \DateTime();
1615
1616 $select = $zdb->select(Adherent::TABLE, 'a');
1617 $select->columns(
1618 array(
1619 'cnt' => new Expression('count(a.' . Adherent::PK . ')')
1620 )
1621 );
1622
1623 $select->join(
1624 array('p' => PREFIX_DB . self::TABLE),
1625 'a.parent_id=p.' . self::PK,
1626 array(),
1627 $select::JOIN_LEFT
1628 );
1629
1630 $select->where
1631 ->lessThan('a.date_echeance', $soon_date->format('Y-m-d'))
1632 ->greaterThanOrEqualTo('a.date_echeance', $now->format('Y-m-d'));
1633 $select
1634 ->where('a.activite_adh=true')
1635 ->where('a.bool_exempt_adh=false');
1636
1637 $select_wo_mail = clone $select;
1638
1639 $select->where(
1640 '(a.email_adh != \'\' OR a.parent_id IS NOT NULL AND p.email_adh != \'\')'
1641 );
1642 $select_wo_mail->where(
1643 '(a.email_adh = \'\' OR a.email_adh IS NULL) AND (p.email_adh = \'\' OR p.email_adh IS NULL)'
1644 );
1645
1646 $results = $zdb->execute($select);
1647 $res = $results->current();
1648 $reminders['impending'] = $res->cnt;
1649
1650 $results_wo_mail = $zdb->execute($select_wo_mail);
1651 $res_wo_mail = $results_wo_mail->current();
1652 $reminders['nomail']['impending'] = $res_wo_mail->cnt;
1653
1654 $select = $zdb->select(Adherent::TABLE, 'a');
1655 $select->columns(
1656 array(
1657 'cnt' => new Expression('count(a.' . Adherent::PK . ')')
1658 )
1659 );
1660
1661 $select->join(
1662 array('p' => PREFIX_DB . self::TABLE),
1663 'a.parent_id=p.' . self::PK,
1664 array(),
1665 $select::JOIN_LEFT
1666 );
1667
1668 $select->where
1669 ->lessThan('a.date_echeance', $now->format('Y-m-d'));
1670 $select
1671 ->where('a.activite_adh=true')
1672 ->where('a.bool_exempt_adh=false');
1673
1674 $select_wo_mail = clone $select;
1675
1676 $select->where(
1677 '(a.email_adh != \'\' OR a.parent_id IS NOT NULL AND p.email_adh != \'\')'
1678 );
1679
1680 $select_wo_mail->where(
1681 '(a.email_adh = \'\' OR a.email_adh IS NULL) AND (p.email_adh = \'\' OR p.email_adh IS NULL)'
1682 );
1683
1684 $results = $zdb->execute($select);
1685 $res = $results->current();
1686 $reminders['late'] = $res->cnt;
1687
1688 $results_wo_mail = $zdb->execute($select_wo_mail);
1689 $res_wo_mail = $results_wo_mail->current();
1690 $reminders['nomail']['late'] = $res_wo_mail->cnt;
1691
1692 return $reminders;
1693 }
1694
1695 /**
1696 * Get count for current query
1697 *
1698 * @return int
1699 */
1700 public function getCount()
1701 {
1702 return $this->count;
1703 }
1704
1705 /**
1706 * Get registered errors
1707 *
1708 * @return array
1709 */
1710 public function getErrors()
1711 {
1712 return $this->errors;
1713 }
1714
1715 /**
1716 * Get all existing emails
1717 *
1718 * @param Db $zdb Database instance
1719 *
1720 * @return array ['email' => 'id_adh']
1721 */
1722 public static function getEmails(Db $zdb)
1723 {
1724 $emails = [];
1725 $select = $zdb->select(self::TABLE);
1726 $select->columns([
1727 self::PK,
1728 'email_adh'
1729 ]);
1730 $select->where('email_adh != \'\' AND email_adh IS NOT NULL');
1731 $rows = $zdb->execute($select);
1732 foreach ($rows as $row) {
1733 $emails[$row->email_adh] = $row->{self::PK};
1734 }
1735 return $emails;
1736 }
1737
1738 /**
1739 * Get current filters
1740 *
1741 * @return MembersList
1742 */
1743 public function getFilters()
1744 {
1745 return $this->filters;
1746 }
1747
1748 /**
1749 * Get members list to instanciate dropdowns
1750 *
1751 * @param Db $zdb Database instance
1752 * @param integer $current Current member
1753 *
1754 * @return array
1755 */
1756 public function getSelectizedMembers(Db $zdb, $current = null)
1757 {
1758 $members = [];
1759 $required_fields = array(
1760 'id_adh',
1761 'nom_adh',
1762 'prenom_adh',
1763 'pseudo_adh'
1764 );
1765 $list_members = $this->getList(false, $required_fields);
1766
1767 if (count($list_members) > 0) {
1768 foreach ($list_members as $member) {
1769 $pk = Adherent::PK;
1770
1771 $members[$member->$pk] = Adherent::getNameWithCase(
1772 $member->nom_adh,
1773 $member->prenom_adh,
1774 false,
1775 $member->id_adh,
1776 $member->pseudo_adh
1777 );
1778 }
1779 }
1780
1781 //check if current attached member is part of the list
1782 if ($current !== null && !isset($members[$current])) {
1783 $members =
1784 [$current => Adherent::getSName($zdb, $current, true, true)] +
1785 $members
1786 ;
1787 }
1788
1789 return $members;
1790 }
1791 }