4 * Copyright © 2003-2024 The Galette Team
6 * This file is part of Galette (https://galette.eu).
8 * Galette is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
13 * Galette is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with Galette. If not, see <http://www.gnu.org/licenses/>.
22 namespace Galette\Filters
;
26 use Galette\Entity\Status
;
27 use Galette\Entity\ContributionsTypes
;
28 use Galette\Entity\Contribution
;
29 use Galette\Repository\Members
;
30 use Galette\DynamicFields\DynamicField
;
31 use Galette\Repository\PaymentTypes
;
34 * Members list filters and paginator
36 * @author Johan Cwiklinski <johan@x-tnd.be>
38 * @property ?string $creation_date_begin
39 * @property ?string $creation_date_end
40 * @property ?string $modif_date_begin
41 * @property ?string $modif_date_end
42 * @property ?string $due_date_begin
43 * @property ?string $due_date_end
44 * @property ?string $birth_date_begin
45 * @property ?string $birth_date_end
46 * @property int $show_public_infos
47 * @property array|integer $status
48 * @property ?string $contrib_creation_date_begin
49 * @property ?string $contrib_creation_date_end
50 * @property ?string $contrib_begin_date_begin
51 * @property ?string $contrib_begin_date_end
52 * @property ?string $contrib_end_date_begin
53 * @property ?string $contrib_end_date_end
54 * @property array $contributions_types
55 * @property array $payments_types
56 * @property ?float $contrib_min_amount
57 * @property ?float $contrib_max_amount
58 * @property array $contrib_dynamic
59 * @property array $free_search
60 * @property array $groups_search
61 * @property integer $groups_search_log_op
63 * @property-read ?string $rcreation_date_begin
64 * @property-read ?string $rcreation_date_end
65 * @property-read ?string $rmodif_date_begin
66 * @property-read ?string $rmodif_date_end
67 * @property-read ?string $rdue_date_begin
68 * @property-read ?string $rdue_date_end
69 * @property-read ?string $rbirth_date_begin
70 * @property-read ?string $rbirth_date_end
71 * @property-read ?string $rcontrib_creation_date_begin
72 * @property-read ?string $rcontrib_creation_date_end
73 * @property-read ?string $rcontrib_begin_date_begin
74 * @property-read ?string $rcontrib_begin_date_end
75 * @property-read ?string $rcontrib_end_date_begin
76 * @property-read ?string $rcontrib_end_date_end
77 * @property-read array $search_fields
80 class AdvancedMembersList
extends MembersList
82 public const OP_AND
= 0;
83 public const OP_OR
= 1;
85 public const OP_EQUALS
= 0;
86 public const OP_CONTAINS
= 1;
87 public const OP_NOT_EQUALS
= 2;
88 public const OP_NOT_CONTAINS
= 3;
89 public const OP_STARTS_WITH
= 4;
90 public const OP_ENDS_WITH
= 5;
91 public const OP_BEFORE
= 6;
92 public const OP_AFTER
= 7;
94 private ?
string $creation_date_begin = null;
95 private ?
string $creation_date_end = null;
96 private ?
string $modif_date_begin = null;
97 private ?
string $modif_date_end = null;
98 private ?
string $due_date_begin = null;
99 private ?
string $due_date_end = null;
100 private ?
string $birth_date_begin = null;
101 private ?
string $birth_date_end = null;
102 private int $show_public_infos = Members
::FILTER_DC_PUBINFOS
;
103 /** @var array<int> */
104 private array $status = array();
105 private ?
string $contrib_creation_date_begin = null;
106 private ?
string $contrib_creation_date_end = null;
107 private ?
string $contrib_begin_date_begin = null;
108 private ?
string $contrib_begin_date_end = null;
109 private ?
string $contrib_end_date_begin = null;
110 private ?
string $contrib_end_date_end = null;
111 /** @var array<int> */
112 private array $contributions_types = array();
113 /** @var array<int> */
114 private array $payments_types = array();
115 private ?
float $contrib_min_amount = null;
116 private ?
float $contrib_max_amount = null;
118 /** @var array<string> */
119 protected array $advancedmemberslist_fields = array(
120 'creation_date_begin',
130 'contrib_creation_date_begin',
131 'contrib_creation_date_end',
132 'contrib_begin_date_begin',
133 'contrib_begin_date_end',
134 'contrib_end_date_begin',
135 'contrib_end_date_end',
136 'contributions_types',
138 'contrib_min_amount',
139 'contrib_max_amount',
143 'groups_search_log_op'
146 /** @var array<string> */
147 protected array $virtuals_advancedmemberslist_fields = array(
148 'rcreation_date_begin',
149 'rcreation_date_end',
156 'rcontrib_creation_date_begin',
157 'rcontrib_creation_date_end',
158 'rcontrib_begin_date_begin',
159 'rcontrib_begin_date_end',
160 'rcontrib_end_date_begin',
161 'rcontrib_end_date_end',
166 * an empty free search criteria to begin
168 * @var array<string,mixed>
170 private array $free_search = array(
174 'log_op' => self
::OP_AND
,
175 'qry_op' => self
::OP_EQUALS
180 * an empty group search criteria to begin
182 * @var array<string,mixed>
184 private array $groups_search = array(
190 //defaults to 'OR' for group search
191 private int $groups_search_log_op = self
::OP_OR
;
194 * an empty contributions dynamic field criteria to begin
196 * @var array<string,mixed>
198 private array $contrib_dynamic = array();
201 * Default constructor
203 * @param ?MembersList $simple A simple filter search to keep
205 public function __construct(MembersList
$simple = null)
207 parent
::__construct();
208 if ($simple instanceof MembersList
) {
209 foreach ($this->pagination_fields
as $pf) {
210 $this->$pf = $simple->$pf;
212 foreach ($this->memberslist_fields
as $mlf) {
213 $this->$mlf = $simple->$mlf;
219 * Do we want to filter within contributions?
223 public function withinContributions(): bool
226 $this->contrib_creation_date_begin
!= null
227 ||
$this->contrib_creation_date_end
!= null
228 ||
$this->contrib_begin_date_begin
!= null
229 ||
$this->contrib_begin_date_end
!= null
230 ||
$this->contrib_end_date_begin
!= null
231 ||
$this->contrib_end_date_end
!= null
232 ||
$this->contrib_min_amount
!= null
233 ||
$this->contrib_max_amount
!= null
234 ||
count($this->contrib_dynamic
) > 0
235 ||
count($this->contributions_types
) > 0
236 ||
count($this->payments_types
) > 0
245 * Reinit default parameters
249 public function reinit(): void
253 $this->creation_date_begin
= null;
254 $this->creation_date_end
= null;
255 $this->modif_date_begin
= null;
256 $this->modif_date_end
= null;
257 $this->due_date_begin
= null;
258 $this->due_date_end
= null;
259 $this->birth_date_begin
= null;
260 $this->birth_date_end
= null;
261 $this->show_public_infos
= Members
::FILTER_DC_PUBINFOS
;
262 $this->status
= array();
264 $this->contrib_creation_date_begin
= null;
265 $this->contrib_creation_date_end
= null;
266 $this->contrib_begin_date_begin
= null;
267 $this->contrib_begin_date_end
= null;
268 $this->contrib_end_date_begin
= null;
269 $this->contrib_begin_date_end
= null;
270 $this->contributions_types
= array();
271 $this->payments_types
= array();
273 $this->free_search
= array(
277 'log_op' => self
::OP_AND
,
278 'qry_op' => self
::OP_EQUALS
282 $this->contrib_dynamic
= array();
284 $this->groups_search
= array(
290 $this->groups_search_log_op
= self
::OP_OR
;
294 * Global getter method
296 * @param string $name name of the property we want to retrieve
298 * @return mixed the called property
300 public function __get(string $name): mixed
303 in_array($name, $this->pagination_fields
)
304 ||
in_array($name, $this->memberslist_fields
)
306 return parent
::__get($name);
309 in_array($name, $this->advancedmemberslist_fields
)
310 ||
in_array($name, $this->virtuals_advancedmemberslist_fields
)
313 case 'creation_date_begin':
314 case 'creation_date_end':
315 case 'modif_date_begin':
316 case 'modif_date_end':
317 case 'due_date_begin':
319 case 'birth_date_begin':
320 case 'birth_date_end':
321 case 'contrib_creation_date_begin':
322 case 'contrib_creation_date_end':
323 case 'contrib_begin_date_begin':
324 case 'contrib_begin_date_end':
325 case 'contrib_end_date_begin':
326 case 'contrib_end_date_end':
328 if ($this->$name !== null) {
329 $d = new \
DateTime($this->$name);
330 return $d->format(__("Y-m-d"));
332 } catch (Throwable
$e) {
333 //oops, we've got a bad date :/
335 'Bad date (' . $this->$name . ') | ' .
342 case 'rcreation_date_begin':
343 case 'rcreation_date_end':
344 case 'rmodif_date_begin':
345 case 'rmodif_date_end':
346 case 'rdue_date_begin':
347 case 'rdue_date_end':
348 case 'rbirth_date_begin':
349 case 'rbirth_date_end':
350 case 'rcontrib_creation_date_begin':
351 case 'rcontrib_creation_date_end':
352 case 'rcontrib_begin_date_begin':
353 case 'rcontrib_begin_date_end':
354 case 'rcontrib_end_date_begin':
355 case 'rcontrib_end_date_end':
356 $rname = substr($name, 1);
357 return $this->$rname;
358 case 'search_fields':
359 $search_fields = array_merge($this->memberslist_fields
, $this->advancedmemberslist_fields
);
360 $key = array_search('selected', $search_fields);
361 unset($search_fields[$key]);
362 $key = array_search('unreachable', $search_fields);
363 unset($search_fields[$key]);
364 $key = array_search('query', $search_fields);
365 unset($search_fields[$key]);
366 return $search_fields;
372 throw new \
RuntimeException(
374 'Unable to get property "%s::%s"!',
382 * Global isset method
383 * Required for twig to access properties via __get
385 * @param string $name name of the property we want to retrieve
389 public function __isset(string $name): bool
392 in_array($name, $this->pagination_fields
)
393 ||
in_array($name, $this->memberslist_fields
)
398 in_array($name, $this->advancedmemberslist_fields
)
399 ||
in_array($name, $this->virtuals_advancedmemberslist_fields
)
409 * Global setter method
411 * @param string $name name of the property we want to assign a value to
412 * @param mixed $value a relevant value for the property
416 public function __set(string $name, mixed $value): void
418 global $zdb, $preferences, $login;
421 in_array($name, $this->pagination_fields
)
422 ||
in_array($name, $this->memberslist_fields
)
424 parent
::__set($name, $value);
427 '[AdvancedMembersList] Setting property `' . $name . '`',
432 case 'creation_date_begin':
433 case 'creation_date_end':
434 case 'modif_date_begin':
435 case 'modif_date_end':
436 case 'due_date_begin':
438 case 'birth_date_begin':
439 case 'birth_date_end':
440 case 'contrib_creation_date_begin':
441 case 'contrib_creation_date_end':
442 case 'contrib_begin_date_begin':
443 case 'contrib_begin_date_end':
444 case 'contrib_end_date_begin':
445 case 'contrib_end_date_end':
446 if ($value !== null && trim($value) !== '') {
448 $d = \DateTime
::createFromFormat(__("Y-m-d"), $value);
450 throw new \
Exception('Incorrect format');
452 $this->$name = $d->format('Y-m-d');
453 } catch (Throwable
$e) {
455 'Incorrect date format for ' . $name .
462 case 'contrib_min_amount':
463 case 'contrib_max_amount':
464 if (is_float($value)) {
465 $this->$name = $value;
467 if ($value !== null) {
469 'Incorrect amount for ' . $name . '! ' .
470 'Should be a float (' . gettype($value) . ' given)',
476 case 'show_public_infos':
477 if (is_numeric($value)) {
478 $this->$name = $value;
481 '[AdvancedMembersList] Value for property `' . $name .
482 '` should be an integer (' . gettype($value) . ' given)',
488 if (!is_array($value)) {
489 $value = array($value);
491 $this->status
= array();
492 foreach ($value as $v) {
493 if (is_numeric($v)) {
494 //check status existence
495 $s = new Status($zdb);
497 if ($res !== false) {
498 $this->status
[] = $v;
501 'Status #' . $v . ' does not exists!',
507 '[AdvancedMembersList] Value for status filter should be an '
508 . 'integer (' . gettype($v) . ' given',
514 case 'contributions_types':
515 if (!is_array($value)) {
516 $value = array($value);
518 $this->contributions_types
= array();
519 foreach ($value as $v) {
520 if (is_numeric($v)) {
521 //check type existence
522 $s = new ContributionsTypes($zdb);
524 if ($res !== false) {
525 $this->contributions_types
[] = $v;
528 'Contribution type #' . $v . ' does not exists!',
534 '[AdvancedMembersList] Value for contribution type '
535 . 'filter should be an integer (' . gettype($v) .
542 case 'payments_types':
543 if (!is_array($value)) {
544 $value = array($value);
546 $this->payments_types
= array();
547 $ptypes = new PaymentTypes(
552 $ptlist = $ptypes->getList();
554 foreach ($value as $v) {
555 if (is_numeric($v)) {
556 if (isset($ptlist[$v])) {
557 $this->payments_types
[] = $v;
560 'Payment type #' . $v . ' does not exists!',
566 '[AdvancedMembersList] Value for payment type filter should be an '
567 . 'integer (' . gettype($v) . ' given',
574 if (isset($this->free_search
['empty']) && !isset($value['empty'])) {
575 unset($this->free_search
['empty']);
578 if ($this->isValidFreeSearch($value)) {
579 //should this happen?
585 foreach ($values as $value) {
586 if ($this->isValidFreeSearch($value)) {
589 //handle value according to type
590 switch ($value['type']) {
591 case DynamicField
::DATE
:
592 if ($value['search'] !== null && trim($value['search']) !== '') {
594 $d = \DateTime
::createFromFormat(__("Y-m-d"), $value['search']);
596 throw new \
Exception('Incorrect format');
598 $value['search'] = $d->format('Y-m-d');
599 } catch (Throwable
$e) {
601 'Incorrect date format for ' . $value['field'] .
602 '! was: ' . $value['search'],
610 $this->free_search
[$id] = $value;
613 '[AdvancedMembersList] bad construct for free filter',
619 case 'contrib_dynamic':
620 if (is_array($value)) {
621 $this->contrib_dynamic
= $value;
624 '[AdvancedMembersList] Value for dynamic contribution fields filter should be an '
625 . 'array (' . gettype($value) . ' given',
630 case 'groups_search':
631 if (isset($this->groups_search
['empty'])) {
632 unset($this->groups_search
['empty']);
634 if (is_array($value)) {
636 isset($value['group'])
637 && isset($value['idx'])
640 unset($value['idx']);
641 $this->groups_search
[$id] = $value;
644 '[AdvancedMembersList] bad construct for group filter',
650 '[AdvancedMembersList] Value for group filter should be an '
651 . 'array (' . gettype($value) . ' given',
656 case 'groups_search_log_op':
657 if ($value == self
::OP_AND ||
$value == self
::OP_OR
) {
658 $this->groups_search_log_op
= $value;
661 '[AdvancedMembersList] Value for group filter logical operator should be '
662 . ' in [0,1] (' . gettype($value) . '-> ' . $value . ' given )',
669 substr($name, 0, 4) === 'cds_'
670 ||
substr($name, 0, 5) === 'cdsc_'
672 if (is_array($value) ||
trim($value) !== '') {
674 if (substr($name, 0, 5) === 'cdsc_') {
675 $id = substr($name, 5, strlen($name));
677 $id = substr($name, 4, strlen($name));
679 $this->contrib_dynamic
[$id] = $value;
683 '[AdvancedMembersList] Unable to set property `' .
694 * Validate free search internal array
696 * @param array<string,mixed> $data Array to validate
700 public static function isValidFreeSearch(array $data): bool
702 return isset($data['field'])
703 && isset($data['search'])
704 && isset($data['log_op'])
705 && isset($data['qry_op'])
706 && isset($data['idx'])
707 && isset($data['type']);