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