3 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
10 * Copyright © 2009-2021 The Galette Team
12 * This file is part of Galette (http://galette.tuxfamily.org).
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.
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.
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/>.
27 * @category Repository
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
37 namespace Galette\Repository
;
39 use Galette\Entity\Social
;
41 use Galette\DynamicFields\DynamicField
;
42 use Galette\Entity\DynamicFieldsHandle
;
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
;
62 * Members class for galette
65 * @category Repository
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
75 public const TABLE
= Adherent
::TABLE
;
76 public const PK
= Adherent
::PK
;
78 public const ALL_ACCOUNTS
= 0;
79 public const ACTIVE_ACCOUNT
= 1;
80 public const INACTIVE_ACCOUNT
= 2;
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;
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;
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;
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';
119 public const NON_STAFF_MEMBERS
= 30;
121 private $filters = false;
122 private $count = null;
123 private $errors = array();
126 * Default constructor
128 * @param MembersList $filters Filtering
130 public function __construct($filters = null)
132 if ($filters === null) {
133 $this->filters
= new MembersList();
135 $this->filters
= $filters;
140 * Get staff members list
142 * @param bool $as_members return the results as an array of
144 * @param array $fields field(s) name(s) to get. Should be a string or
145 * an array. If null, all fields will be
147 * @param boolean $count true if we want to count members
148 * @param boolean $limit true to LIMIT query
150 * @return Adherent[]|ResultSet
152 public function getStaffMembersList(
158 return $this->getMembersList(
169 * Get managed members list (for groups managers)
171 * @param bool $as_members return the results as an array of
173 * @param array $fields field(s) name(s) to get. Should be a string or
174 * an array. If null, all fields will be
176 * @param boolean $count true if we want to count members
177 * @param boolean $limit true to LIMIT query
179 * @return Adherent[]|ResultSet
181 public function getManagedMembersList(
187 return $this->getMembersList(
200 * @param bool $as_members return the results as an array of
202 * @param array $fields field(s) name(s) to get. Should be a string or
203 * an array. If null, all fields will be
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
211 * @return Adherent[]|Laminas\Db\ResultSet
213 public function getMembersList(
224 if ($limit === true) {
225 //force count if limit is active
230 $_mode = self
::SHOW_LIST
;
231 if ($staff !== false) {
232 $_mode = self
::SHOW_STAFF
;
234 if ($managed !== false) {
235 $_mode = self
::SHOW_MANAGED
;
237 if ($export !== false) {
238 $_mode = self
::SHOW_EXPORT
;
241 $select = $this->buildSelect(
248 //add limits to retrieve only relavant rows
249 if ($limit === true) {
250 $this->filters
->setLimits($select);
253 $rows = $zdb->execute($select);
254 $this->filters
->query
= $zdb->query_string
;
262 foreach ($rows as $row) {
263 $members[] = new Adherent($zdb, $row, $deps);
269 } catch (Throwable
$e) {
271 'Cannot list members | ' . $e->getMessage(),
279 * Remove specified members
281 * @param integer|array $ids Members identifiers to delete
285 public function removeMembers($ids)
287 global $zdb, $hist, $emitter;
289 $processed = array();
291 if (is_array($ids)) {
293 } elseif (is_numeric($ids)) {
300 $zdb->connection
->beginTransaction();
302 //Retrieve some information
303 $select = $zdb->select(self
::TABLE
);
305 array(self
::PK
, 'nom_adh', 'prenom_adh', 'email_adh')
306 )->where
->in(self
::PK
, $list);
308 $results = $zdb->execute($select);
311 foreach ($results as $member) {
312 $str_adh = $member->id_adh
. ' (' . $member->nom_adh
. ' ' .
313 $member->prenom_adh
. ')';
314 $infos .= $str_adh . "\n";
316 $p = new Picture($member->id_adh
);
317 if ($p->hasPicture()) {
318 if (!$p->delete(false)) {
320 'Unable to delete picture for member ' . $str_adh,
323 throw new \
Exception(
324 'Unable to delete picture for member ' .
329 _T("Member Picture deleted"),
336 'id_adh' => $member->id_adh
,
337 'nom_adh' => $member->nom_adh
,
338 'prenom_adh' => $member->prenom_adh
,
339 'email_adh' => $member->email_adh
343 //delete contributions
344 $del_qry = $zdb->delete(Contribution
::TABLE
);
349 $zdb->execute($del_qry);
352 $select = $zdb->select(Transaction
::TABLE
);
353 $select->where
->in(self
::PK
, $list);
354 $results = $zdb->execute($select);
356 //if members has transactions;
357 //reset link with other contributions
359 if ($results->count() > 0) {
361 foreach ($results as $transaction) {
362 $transactions[] = $transaction[Transaction
::PK
];
365 $update = $zdb->update(Contribution
::TABLE
);
367 Transaction
::PK
=> new Expression('NULL')
372 $zdb->execute($update);
375 //delete transactions
376 $del_qry = $zdb->delete(Transaction
::TABLE
);
377 $del_qry->where
->in(self
::PK
, $list);
378 $zdb->execute($del_qry);
380 //delete groups membership/mamagmentship
381 Groups
::removeMembersFromGroups($list);
384 $del_qry = $zdb->delete(Reminder
::TABLE
);
389 $zdb->execute($del_qry);
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);
398 $del_qry = $zdb->delete(self
::TABLE
);
403 $zdb->execute($del_qry);
406 $zdb->connection
->commit();
408 foreach ($processed as $p) {
409 $emitter->emit('member.remove', $p);
412 //add an history entry
414 _T("Delete members cards, transactions and dues"),
419 } catch (Throwable
$e) {
420 $zdb->connection
->rollBack();
421 if ($e->getCode() == 23000) {
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 ' .
429 $this->errors
[] = _T("Cannot remove a member who still have dependencies (mailings, ...)");
432 'Unable to delete selected member(s) |' .
445 * @param boolean $as_members return the results as an array of
447 * @param array $fields field(s) name(s) to get. Should be a string or
448 * an array. If null, all fields will be
451 * @return Adherent[]|ResultSet
453 public function getList($as_members = false, $fields = null)
455 return $this->getMembersList(
467 * Get members list with public information available
469 * @param boolean $with_photos get only members which have uploaded a
470 * photo (for trombinoscope)
474 public function getPublicList($with_photos)
479 $select = $this->buildSelect(
480 self
::SHOW_PUBLIC_LIST
,
486 $this->filters
->setLimits($select);
488 $results = $zdb->execute($select);
493 'picture' => $with_photos
495 foreach ($results as $row) {
496 $members[] = new Adherent($zdb, $row, $deps);
499 } catch (Throwable
$e) {
501 'Cannot list members with public information (photos: '
502 . $with_photos . ') | ' . $e->getMessage(),
510 * Get list of members that has been selected
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
523 public function getArrayList(
526 $with_photos = false,
535 if (!is_array($ids) ||
count($ids) < 1) {
536 Analog
::log('No member selected for labels.', Analog
::INFO
);
541 $damode = self
::SHOW_ARRAY_LIST
;
542 if ($export === true) {
543 $damode = self
::SHOW_EXPORT
;
545 $select = $this->buildSelect(
551 $select->where
->in('a.' . self
::PK
, $ids);
552 if (is_array($orderby) && count($orderby) > 0) {
553 foreach ($orderby as $o) {
558 $results = $zdb->execute($select);
562 'picture' => $with_photos,
567 foreach ($results as $o) {
568 if ($as_members === true) {
569 $members[] = new Adherent($zdb, $o, $deps);
575 } catch (Throwable
$e) {
577 'Cannot load members form ids array | ' . $e->getMessage(),
585 * Builds the SELECT statement
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
593 * @return Select SELECT statement
595 private function buildSelect($mode, $fields, $photos, $count = false): Select
600 if ($fields != null && is_array($fields) && !in_array('id_adh', $fields)) {
601 $fields[] = 'id_adh';
603 $fieldsList = ($fields != null)
604 ?
((!is_array($fields) ||
count($fields) < 1) ?
(array)'*'
605 : $fields) : (array)'*';
607 $select = $zdb->select(self
::TABLE
, 'a');
609 $select->columns($fieldsList);
611 $select->quantifier('DISTINCT');
614 case self
::SHOW_STAFF
:
615 case self
::SHOW_LIST
:
616 case self
::SHOW_ARRAY_LIST
:
618 array('p' => PREFIX_DB
. Status
::TABLE
),
619 'a.' . Status
::PK
. '=p.' . Status
::PK
,
622 array('so' => PREFIX_DB
. Social
::TABLE
),
623 'a.' . Adherent
::PK
. '=so.' . Adherent
::PK
,
628 case self
::SHOW_EXPORT
:
629 //basically the same as above, but without any fields
631 array('p' => PREFIX_DB
. Status
::TABLE
),
632 'a.' . Status
::PK
. '=p.' . Status
::PK
,
636 case self
::SHOW_MANAGED
:
638 array('p' => PREFIX_DB
. Status
::TABLE
),
639 'a.' . Status
::PK
. '=p.' . Status
::PK
641 array('so' => PREFIX_DB
. Social
::TABLE
),
642 'a.' . Adherent
::PK
. '=so.' . Adherent
::PK
,
646 array('gr' => PREFIX_DB
. Group
::GROUPSUSERS_TABLE
),
647 'a.' . Adherent
::PK
. '=gr.' . Adherent
::PK
,
650 array('m' => PREFIX_DB
. Group
::GROUPSMANAGERS_TABLE
),
651 'gr.' . Group
::PK
. '=m.' . Group
::PK
,
653 )->where('m.' . Adherent
::PK
. ' = ' . $login->id
);
655 case self
::SHOW_PUBLIC_LIST
:
658 array('p' => PREFIX_DB
. Picture
::TABLE
),
659 'a.' . self
::PK
. '= p.' . self
::PK
,
666 //check for contributions filtering
668 $this->filters
instanceof AdvancedMembersList
669 && $this->filters
->withinContributions()
672 array('ct' => PREFIX_DB
. Contribution
::TABLE
),
673 'ct.' . self
::PK
. '=a.' . self
::PK
,
679 //check if there are dynamic fields in filter
682 if ($this->filters
instanceof AdvancedMembersList
) {
684 (bool)count($this->filters
->free_search
)
685 && !isset($this->filters
->free_search
['empty'])
687 $free_searches = $this->filters
->free_search
;
688 foreach ($free_searches as $fs) {
689 if (strpos($fs['field'], 'dyn_') === 0) {
690 // simple dynamic fields
692 $dfs[] = str_replace('dyn_', '', $fs['field']);
698 //check if there are dynamic fields for contributions in filter
704 $this->filters
instanceof AdvancedMembersList
705 && $this->filters
->withinContributions()
708 $this->filters
->contrib_dynamic
709 && count($this->filters
->contrib_dynamic
) > 0
710 && !isset($this->filters
->contrib_dynamic
['empty'])
714 //check if there are dynamic fields in the filter
715 foreach ($this->filters
->contrib_dynamic
as $k => $cd) {
724 if ($hasDfc === true ||
$hasCdfc === true) {
726 array('dfc' => PREFIX_DB
. DynamicFieldsHandle
::TABLE
),
727 'dfc.item_id=ct.' . Contribution
::PK
,
733 // simple dynamic fields
734 if ($hasDf === true) {
735 foreach ($dfs as $df) {
736 $subselect = $zdb->select(DynamicFieldsHandle
::TABLE
, 'df');
739 'item_id' => 'item_id',
743 $subselect->where('df.field_form = \'adh\'');
744 $subselect->where('df.field_id = ' . $df);
746 array('df' . $df => $subselect),
747 'a.id_adh = df' . $df . '.item_id',
754 // choice dynamic fields
755 if ($hasCdfc === true) {
756 $cdf_field = 'cdf.id';
757 if (TYPE_DB
=== 'pgsql') {
758 $cdf_field .= '::text';
761 $cdf_field = 'cdfc.id';
762 if (TYPE_DB
=== 'pgsql') {
763 $cdf_field .= '::text';
765 foreach ($cdfcs as $cdf) {
766 $rcdf_field = str_replace(
772 array('cdfc' . $cdf => DynamicField
::getFixedValuesTableName($cdf, true)),
773 $rcdf_field . '=dfc.field_val',
780 if ($mode == self
::SHOW_LIST ||
$mode == self
::SHOW_MANAGED
) {
781 if ($this->filters
!== false) {
782 $this->buildWhereClause($select);
784 } elseif ($mode == self
::SHOW_PUBLIC_LIST
) {
797 new Expression('true')
807 new Expression('true')
812 new Expression('true')
821 if ($mode === self
::SHOW_STAFF
) {
822 $select->where
->lessThan(
824 self
::NON_STAFF_MEMBERS
829 $this->proceedCount($select);
832 $this->buildOrderClause($select, $fields);
835 } catch (Throwable
$e) {
837 'Cannot build SELECT clause for members | ' . $e->getMessage(),
845 * Count members from the query
847 * @param Select $select Original select
851 private function proceedCount(Select
$select)
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) {
869 unset($join['columns']);
871 $countSelect->columns(
873 'count' => new Expression('count(DISTINCT a.' . self
::PK
. ')')
877 $have = $select->having
;
878 if ($have->count() > 0) {
879 foreach ($have->getPredicates() as $h) {
880 $countSelect->where($h);
884 $results = $zdb->execute($countSelect);
886 $this->count
= (int)$results->current()->count
;
887 if (isset($this->filters
) && $this->count
> 0) {
888 $this->filters
->setCounter($this->count
);
890 } catch (Throwable
$e) {
892 'Cannot count members | ' . $e->getMessage(),
900 * Builds the order clause
902 * @param Select $select Original select
903 * @param array $fields Fields list to ensure ORDER clause
904 * references selected fields. Optional.
908 private function buildOrderClause(Select
$select, $fields = null): Select
912 switch ($this->filters
->orderby
) {
913 case self
::ORDERBY_NICKNAME
:
914 if ($this->canOrderBy('pseudo_adh', $fields)) {
915 $order[] = 'pseudo_adh ' . $this->filters
->getDirection();
918 case self
::ORDERBY_STATUS
:
919 if ($this->canOrderBy('priorite_statut', $fields)) {
920 $order[] = 'priorite_statut ' . $this->filters
->getDirection();
923 case self
::ORDERBY_MODIFDATE
:
924 if ($this->canOrderBy('date_modif_adh', $fields)) {
925 $order[] = 'date_modif_adh ' . $this->filters
->getDirection();
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();
934 if ($this->canOrderBy('date_echeance', $fields)) {
935 $order[] = 'date_echeance ' . $this->filters
->getDirection();
938 case self
::ORDERBY_ID
:
939 if ($this->canOrderBy('id_adh', $fields)) {
940 $order[] = 'id_adh ' . $this->filters
->getDirection();
943 case 'list_adh_name':
946 case self
::ORDERBY_NAME
:
950 if ($this->canOrderBy($this->filters
->orderby
, $fields)) {
951 $order[] = $this->filters
->orderby
. ' ' . $this->filters
->getDirection();
956 //anyways, we want to order by firstname, lastname
957 if ($this->canOrderBy('nom_adh', $fields)) {
958 $order[] = 'nom_adh ' . $this->filters
->getDirection();
960 if ($this->canOrderBy('prenom_adh', $fields)) {
961 $order[] = 'prenom_adh ' . $this->filters
->getDirection();
964 $select->order($order);
969 * Is field allowed to order? it shoulsd be present in
970 * provided fields list (those that are SELECT'ed).
972 * @param string $field_name Field name to order by
973 * @param array $fields SELECTE'ed fields
977 private function canOrderBy($field_name, $fields)
979 if ($fields === null) {
981 } elseif (!is_array($fields)) {
983 } elseif (in_array($field_name, $fields)) {
987 'Trying to order by ' . $field_name . ' while it is not in ' .
996 * Builds where clause, for filtering on simple list mode
998 * @param Select $select Original select
1002 private function buildWhereClause(Select
$select)
1004 global $zdb, $login;
1007 if ($this->filters
->email_filter
== self
::FILTER_W_EMAIL
) {
1008 $select->where('email_adh != \'\'');
1010 if ($this->filters
->email_filter
== self
::FILTER_WO_EMAIL
) {
1011 $select->where('(email_adh = \'\' OR email_adh IS NULL)');
1014 if ($this->filters
->filter_str
!= '') {
1015 $token = $zdb->platform
->quoteValue(
1016 '%' . strtolower($this->filters
->filter_str
) . '%'
1018 switch ($this->filters
->field_filter
) {
1019 case self
::FILTER_NAME
:
1020 if (TYPE_DB
=== 'pgsql') {
1021 $sep = " || ' ' || ";
1032 $pre . 'LOWER(nom_adh)' . $sep .
1033 'LOWER(prenom_adh)' . $sep .
1034 'LOWER(pseudo_adh)' . $post . ' LIKE ' .
1037 $pre . 'LOWER(prenom_adh)' . $sep .
1038 'LOWER(nom_adh)' . $sep .
1039 'LOWER(pseudo_adh)' . $post . ' LIKE ' .
1044 case self
::FILTER_COMPANY_NAME
:
1046 'LOWER(societe_adh) LIKE ' .
1050 case self
::FILTER_ADDRESS
:
1053 'LOWER(adresse_adh) LIKE ' . $token
1055 'LOWER(adresse2_adh) LIKE ' . $token
1057 'cp_adh LIKE ' . $token
1059 'LOWER(ville_adh) LIKE ' . $token
1061 'LOWER(pays_adh) LIKE ' . $token
1065 case self
::FILTER_MAIL
:
1068 'LOWER(email_adh) LIKE ' . $token
1070 'LOWER(so.url) LIKE ' . $token
1074 case self
::FILTER_JOB
:
1076 'LOWER(prof_adh) LIKE ' . $token
1079 case self
::FILTER_INFOS
:
1081 if ($login->isAdmin() ||
$login->isStaff()) {
1082 $more = ' OR LOWER(info_adh) LIKE ' . $token;
1085 '(LOWER(info_public_adh) LIKE ' .
1086 $token . $more . ')'
1089 case self
::FILTER_NUMBER
:
1090 $select->where
->equalTo('a.id_adh', $this->filters
->filter_str
);
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(
1103 $now->format('Y-m-d')
1104 )->lessThanOrEqualTo(
1106 $duedate->format('Y-m-d')
1109 case self
::MEMBERSHIP_LATE
:
1113 date('Y-m-d', time())
1114 )->equalTo('bool_exempt_adh', new Expression('false'));
1116 case self
::MEMBERSHIP_UP2DATE
:
1118 '(' . 'date_echeance >= \'' . date('Y-m-d', time())
1119 . '\' OR bool_exempt_adh=true)'
1122 case self
::MEMBERSHIP_NEVER
:
1123 $select->where('date_echeance IS NULL')
1124 ->where('bool_exempt_adh = false');
1126 case self
::MEMBERSHIP_STAFF
:
1127 $select->where
->lessThan(
1128 'p.priorite_statut',
1129 self
::NON_STAFF_MEMBERS
1132 case self
::MEMBERSHIP_ADMIN
:
1133 $select->where
->equalTo('bool_admin_adh', true);
1135 case self
::MEMBERSHIP_NONE
:
1136 $select->where
->equalTo('a.id_statut', Status
::DEFAULT_STATUS
);
1141 if ($this->filters
->filter_account
) {
1142 switch ($this->filters
->filter_account
) {
1143 case self
::ACTIVE_ACCOUNT
:
1144 $select->where('activite_adh=true');
1146 case self
::INACTIVE_ACCOUNT
:
1147 $select->where('activite_adh=false');
1152 if ($this->filters
->group_filter
) {
1154 array('g' => PREFIX_DB
. Group
::GROUPSUSERS_TABLE
),
1155 'a.' . Adherent
::PK
. '=g.' . Adherent
::PK
,
1159 array('gs' => PREFIX_DB
. Group
::TABLE
),
1160 'gs.' . Group
::PK
. '=g.' . Group
::PK
,
1164 '(g.' . Group
::PK
. ' = ' . $this->filters
->group_filter
.
1165 ' OR gs.parent_group = NULL OR gs.parent_group = ' .
1166 $this->filters
->group_filter
. ')'
1170 if ($this->filters
instanceof AdvancedMembersList
) {
1171 $this->buildAdvancedWhereClause($select);
1175 } catch (Throwable
$e) {
1177 __METHOD__
. ' | ' . $e->getMessage(),
1185 * Builds where clause, for advanced filtering on simple list mode
1187 * @param Select $select Original select
1191 private function buildAdvancedWhereClause(Select
$select)
1193 global $zdb, $login;
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.
1201 count($this->filters
->groups_search
) > 0
1202 && !isset($this->filters
->groups_search
['empty'])
1207 foreach ($this->filters
->groups_search
as $gs) { // then add a row for each group
1208 $wheregroups[] = $gs['group'];
1211 $gselect = $zdb->select(Group
::GROUPSUSERS_TABLE
, 'gu');
1215 array('g' => PREFIX_DB
. Group
::TABLE
),
1216 'gu.id_group=g.' . Group
::PK
,
1221 'g.id_group' => ':group',
1222 'g.parent_group' => ':pgroup'
1226 $gselect->group(['gu.id_adh']);
1228 $stmt = $zdb->sql
->prepareStatementForSqlObject($gselect);
1232 foreach ($this->filters
->groups_search
as $gs) { // then add a row for each ig/searched group pair
1233 $gresults = $stmt->execute(
1235 'group' => $gs['group'],
1236 'pgroup' => $gs['group']
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;
1246 $ids[$gresult['id_adh']] +
= 1;
1249 case AdvancedMembersList
::OP_OR
:
1250 foreach ($gresults as $gresult) {
1251 $mids[$gresult['id_adh']] = $gresult['id_adh'];
1258 foreach ($ids as $id_adh => $count) {
1259 if ($count == count($wheregroups)) {
1260 $mids[$id_adh] = $id_adh;
1266 //limit on found members
1267 $select->where
->in('a.id_adh', $mids);
1269 //no match in groups, end of game.
1270 $select->where('false = true');
1274 //FIXME: should be retrieved from members_fields
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'
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(
1294 $eprop = "r{$property}_end";
1295 if ($this->filters
->$eprop) {
1296 $d = new \
DateTime($this->filters
->$eprop);
1297 $select->where
->lessThanOrEqualTo(
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');
1309 case self
::FILTER_WO_PUBINFOS
:
1310 $select->where('bool_display_info = false');
1312 case self
::FILTER_DC_PUBINFOS
:
1313 //nothing to do here.
1318 if ($this->filters
->status
) {
1321 $this->filters
->status
1326 $this->filters
->contrib_min_amount
1327 ||
$this->filters
->contrib_max_amount
1329 if ($this->filters
->contrib_min_amount
) {
1330 $select->where
->greaterThanOrEqualTo(
1332 $this->filters
->contrib_min_amount
1335 if ($this->filters
->contrib_max_amount
) {
1336 $select->where
->lessThanOrEqualTo(
1338 $this->filters
->contrib_max_amount
1343 if ($this->filters
->contributions_types
) {
1346 $this->filters
->contributions_types
1350 if ($this->filters
->payments_types
) {
1352 'ct.type_paiement_cotis',
1353 $this->filters
->payments_types
1358 count($this->filters
->contrib_dynamic
) > 0
1359 && !isset($this->filters
->contrib_dynamic
['empty'])
1361 foreach ($this->filters
->contrib_dynamic
as $k => $cd) {
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;
1373 $select->where($qry);
1374 $select->where
->in($prefix . $field, $cd);
1376 //dynamic field spotted!
1378 $qry = 'dfc.field_form = \'contrib\' AND ' .
1379 'dfc.field_id = ' . $k . ' AND ';
1380 $field = 'field_val';
1381 $qry .= 'LOWER(' . $prefix . $field . ') ' .
1383 $select->where($qry . $zdb->platform
->quoteValue('%' . strtolower($cd) . '%'));
1389 count($this->filters
->free_search
) > 0
1390 && !isset($this->filters
->free_search
['empty'])
1392 foreach ($this->filters
->free_search
as $fs) {
1393 $fs['search'] = mb_strtolower($fs['search']);
1395 switch ($fs['qry_op']) {
1396 case AdvancedMembersList
::OP_EQUALS
:
1399 case AdvancedMembersList
::OP_CONTAINS
:
1401 $fs['search'] = '%' . $fs['search'] . '%';
1403 case AdvancedMembersList
::OP_NOT_EQUALS
:
1406 case AdvancedMembersList
::OP_NOT_CONTAINS
:
1408 $fs['search'] = '%' . $fs['search'] . '%';
1410 case AdvancedMembersList
::OP_STARTS_WITH
:
1412 $fs['search'] = $fs['search'] . '%';
1414 case AdvancedMembersList
::OP_ENDS_WITH
:
1416 $fs['search'] = '%' . $fs['search'];
1418 case AdvancedMembersList
::OP_BEFORE
:
1421 case AdvancedMembersList
::OP_AFTER
:
1426 'Unknown query operator: ' . $fs['qry_op'] .
1427 ' (will fallback to equals)',
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';
1445 //handle socials networks
1446 if (strpos($fs['field'], 'socials_') === 0) {
1448 $type = str_replace('socials_', '', $fs['field']);
1450 $fs['field'] = 'url';
1451 $select->where(['so.type' => $type]);
1454 if ($dyn_field && $dyn_field instanceof \Galette\DynamicFields\Boolean
) {
1455 if ($fs['search'] != 0) {
1456 $qry .= $prefix . $fs['field'] . $qop . ' ' .
1459 $qry .= $prefix . $fs['field'] . ' IS NULL';
1461 } elseif (!strncmp($fs['field'], 'bool_', strlen('bool_'))) {
1462 $qry .= $prefix . $fs['field'] . $qop . ' ' .
1465 $fs['qry_op'] === AdvancedMembersList
::OP_BEFORE
1466 ||
$fs['qry_op'] === AdvancedMembersList
::OP_AFTER
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']);
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')";
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 . '\') ';
1485 $qry .= $qop . ' ' . $fs['search'];
1487 } elseif ($fs['field'] == 'status_label') {
1488 $qry_pattern = '%p%field %op %value';
1489 $qry .= str_replace(
1500 $zdb->platform
->quoteValue($fs['search'])
1505 $qry .= 'LOWER(' . $prefix . $fs['field'] . ') ' .
1506 $qop . ' ' . $zdb->platform
->quoteValue($fs['search']);
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
);
1521 * Login and password field cannot be empty.
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.
1528 public function emptyLogins()
1533 $zdb->connection
->beginTransaction();
1534 $select = $zdb->select(Adherent
::TABLE
);
1536 array('id_adh', 'login_adh', 'mdp_adh')
1539 'login_adh' => new Expression('NULL'),
1541 'mdp_adh' => new Expression('NULL'),
1547 $results = $zdb->execute($select);
1550 if ($results->count() > 0) {
1551 $update = $zdb->update(Adherent
::TABLE
);
1554 'login_adh' => ':login',
1555 'mdp_adh' => ':pass'
1557 )->where
->equalTo(Adherent
::PK
, ':id');
1559 $stmt = $zdb->sql
->prepareStatementForSqlObject($update);
1561 $p = new \Galette\Core\
Password($zdb);
1563 foreach ($results as $m) {
1567 ||
!isset($m->login_adh
)
1568 ||
$m->login_adh
== 'NULL'
1570 $m->login_adh
= $p->makeRandomPassword(15);
1576 ||
!isset($m->mdp_adh
)
1577 ||
$m->mdp_adh
== 'NULL'
1579 $randomp = $p->makeRandomPassword(15);
1580 $m->mdp_adh
= password_hash(
1587 if ($dirty === true) {
1590 'login' => $m->login_adh
,
1591 'pass' => $m->mdp_adh
,
1599 $zdb->connection
->commit();
1600 $this->count
= $processed;
1602 } catch (Throwable
$e) {
1603 $zdb->connection
->rollBack();
1605 'An error occurred trying to retrieve members with ' .
1606 'empty logins/passwords (' . $e->getMessage(),
1614 * Loads data to produce a Pie chart based on members state of dues
1618 public function getRemindersCount()
1622 $reminders = array();
1624 $soon_date = new \
DateTime();
1625 $soon_date->modify('+1 month');
1627 $now = new \
DateTime();
1629 $select = $zdb->select(Adherent
::TABLE
, 'a');
1632 'cnt' => new Expression('count(a.' . Adherent
::PK
. ')')
1637 array('p' => PREFIX_DB
. self
::TABLE
),
1638 'a.parent_id=p.' . self
::PK
,
1644 ->lessThan('a.date_echeance', $soon_date->format('Y-m-d'))
1645 ->greaterThanOrEqualTo('a.date_echeance', $now->format('Y-m-d'));
1647 ->where('a.activite_adh=true')
1648 ->where('a.bool_exempt_adh=false');
1650 $select_wo_mail = clone $select;
1653 '(a.email_adh != \'\' OR a.parent_id IS NOT NULL AND p.email_adh != \'\')'
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)'
1659 $results = $zdb->execute($select);
1660 $res = $results->current();
1661 $reminders['impending'] = $res->cnt
;
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
;
1667 $select = $zdb->select(Adherent
::TABLE
, 'a');
1670 'cnt' => new Expression('count(a.' . Adherent
::PK
. ')')
1675 array('p' => PREFIX_DB
. self
::TABLE
),
1676 'a.parent_id=p.' . self
::PK
,
1682 ->lessThan('a.date_echeance', $now->format('Y-m-d'));
1684 ->where('a.activite_adh=true')
1685 ->where('a.bool_exempt_adh=false');
1687 $select_wo_mail = clone $select;
1690 '(a.email_adh != \'\' OR a.parent_id IS NOT NULL AND p.email_adh != \'\')'
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)'
1697 $results = $zdb->execute($select);
1698 $res = $results->current();
1699 $reminders['late'] = $res->cnt
;
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
;
1709 * Get count for current query
1713 public function getCount()
1715 return $this->count
;
1719 * Get registered errors
1723 public function getErrors()
1725 return $this->errors
;
1729 * Get all existing emails
1731 * @param Db $zdb Database instance
1733 * @return array ['email' => 'id_adh']
1735 public static function getEmails(Db
$zdb)
1738 $select = $zdb->select(self
::TABLE
);
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
};
1752 * Get current filters
1754 * @return MembersList
1756 public function getFilters()
1758 return $this->filters
;
1762 * Get members list to instanciate dropdowns
1764 * @param Db $zdb Database instance
1765 * @param integer $current Current member
1769 public function getSelectizedMembers(Db
$zdb, $current = null)
1772 $required_fields = array(
1778 $list_members = $this->getList(false, $required_fields);
1780 if (count($list_members) > 0) {
1781 foreach ($list_members as $member) {
1784 $members[$member->$pk] = Adherent
::getNameWithCase(
1786 $member->prenom_adh
,
1794 //check if current attached member is part of the list
1795 if ($current !== null && !isset($members[$current])) {
1797 [$current => Adherent
::getSName($zdb, $current, true, true)] +