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