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