Use UserFilter to generate birthday calendar.
[platal.git] / classes / userfilter.php
CommitLineData
a087cc8d
FB
1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2009 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
d865c296
FB
22
23/******************
24 * CONDITIONS
25 ******************/
26
8363588b 27// {{{ interface UserFilterCondition
2d83cac9
RB
28/** This interface describe objects which filter users based
29 * on various parameters.
30 * The parameters of the filter must be given to the constructor.
31 * The buildCondition function is called by UserFilter when
32 * actually building the query. That function must call
33 * $uf->addWheteverFilter so that the UserFilter makes
34 * adequate joins. It must return the 'WHERE' condition to use
35 * with the filter.
36 */
a087cc8d
FB
37interface UserFilterCondition
38{
5dd9d823
FB
39 const COND_TRUE = 'TRUE';
40 const COND_FALSE = 'FALSE';
41
a087cc8d
FB
42 /** Check that the given user matches the rule.
43 */
784745ce 44 public function buildCondition(UserFilter &$uf);
a087cc8d 45}
8363588b 46// }}}
a087cc8d 47
8363588b 48// {{{ class UFC_OneChild
a087cc8d
FB
49abstract class UFC_OneChild implements UserFilterCondition
50{
51 protected $child;
52
5dd9d823
FB
53 public function __construct($child = null)
54 {
55 if (!is_null($child) && ($child instanceof UserFilterCondition)) {
56 $this->setChild($child);
57 }
58 }
59
a087cc8d
FB
60 public function setChild(UserFilterCondition &$cond)
61 {
62 $this->child =& $cond;
63 }
64}
8363588b 65// }}}
a087cc8d 66
8363588b 67// {{{ class UFC_NChildren
a087cc8d
FB
68abstract class UFC_NChildren implements UserFilterCondition
69{
70 protected $children = array();
71
5dd9d823
FB
72 public function __construct()
73 {
74 $children = func_get_args();
75 foreach ($children as &$child) {
76 if (!is_null($child) && ($child instanceof UserFilterCondition)) {
77 $this->addChild($child);
78 }
79 }
80 }
81
a087cc8d
FB
82 public function addChild(UserFilterCondition &$cond)
83 {
84 $this->children[] =& $cond;
85 }
5dd9d823
FB
86
87 protected function catConds(array $cond, $op, $fallback)
88 {
89 if (count($cond) == 0) {
90 return $fallback;
91 } else if (count($cond) == 1) {
92 return $cond[0];
93 } else {
4927ee54 94 return '(' . implode(') ' . $op . ' (', $cond) . ')';
5dd9d823
FB
95 }
96 }
a087cc8d 97}
8363588b 98// }}}
a087cc8d 99
8363588b 100// {{{ class UFC_True
a087cc8d
FB
101class UFC_True implements UserFilterCondition
102{
784745ce 103 public function buildCondition(UserFilter &$uf)
a087cc8d 104 {
5dd9d823 105 return self::COND_TRUE;
a087cc8d
FB
106 }
107}
8363588b 108// }}}
a087cc8d 109
8363588b 110// {{{ class UFC_False
a087cc8d
FB
111class UFC_False implements UserFilterCondition
112{
784745ce 113 public function buildCondition(UserFilter &$uf)
a087cc8d 114 {
5dd9d823 115 return self::COND_FALSE;
a087cc8d
FB
116 }
117}
8363588b 118// }}}
a087cc8d 119
8363588b 120// {{{ class UFC_Not
a087cc8d
FB
121class UFC_Not extends UFC_OneChild
122{
784745ce 123 public function buildCondition(UserFilter &$uf)
a087cc8d 124 {
5dd9d823
FB
125 $val = $this->child->buildCondition($uf);
126 if ($val == self::COND_TRUE) {
127 return self::COND_FALSE;
128 } else if ($val == self::COND_FALSE) {
129 return self::COND_TRUE;
130 } else {
131 return 'NOT (' . $val . ')';
132 }
a087cc8d
FB
133 }
134}
8363588b 135// }}}
a087cc8d 136
8363588b 137// {{{ class UFC_And
a087cc8d
FB
138class UFC_And extends UFC_NChildren
139{
784745ce 140 public function buildCondition(UserFilter &$uf)
a087cc8d 141 {
784745ce 142 if (empty($this->children)) {
5dd9d823 143 return self::COND_FALSE;
784745ce 144 } else {
5dd9d823 145 $true = self::COND_FALSE;
784745ce
FB
146 $conds = array();
147 foreach ($this->children as &$child) {
5dd9d823
FB
148 $val = $child->buildCondition($uf);
149 if ($val == self::COND_TRUE) {
150 $true = self::COND_TRUE;
151 } else if ($val == self::COND_FALSE) {
152 return self::COND_FALSE;
153 } else {
154 $conds[] = $val;
155 }
a087cc8d 156 }
5dd9d823 157 return $this->catConds($conds, 'AND', $true);
a087cc8d 158 }
a087cc8d
FB
159 }
160}
8363588b 161// }}}
a087cc8d 162
8363588b 163// {{{ class UFC_Or
a087cc8d
FB
164class UFC_Or extends UFC_NChildren
165{
784745ce 166 public function buildCondition(UserFilter &$uf)
a087cc8d 167 {
784745ce 168 if (empty($this->children)) {
5dd9d823 169 return self::COND_TRUE;
784745ce 170 } else {
5dd9d823 171 $true = self::COND_TRUE;
784745ce
FB
172 $conds = array();
173 foreach ($this->children as &$child) {
5dd9d823
FB
174 $val = $child->buildCondition($uf);
175 if ($val == self::COND_TRUE) {
176 return self::COND_TRUE;
177 } else if ($val == self::COND_FALSE) {
178 $true = self::COND_FALSE;
179 } else {
180 $conds[] = $val;
181 }
a087cc8d 182 }
5dd9d823 183 return $this->catConds($conds, 'OR', $true);
a087cc8d 184 }
a087cc8d
FB
185 }
186}
8363588b 187// }}}
a087cc8d 188
8363588b 189// {{{ class UFC_Profile
53eae167
RB
190/** Filters users who have a profile
191 */
eb1449b8
FB
192class UFC_Profile implements UserFilterCondition
193{
194 public function buildCondition(UserFilter &$uf)
195 {
196 return '$PID IS NOT NULL';
197 }
198}
8363588b 199// }}}
eb1449b8 200
8363588b 201// {{{ class UFC_Promo
5d2e55c7 202/** Filters users based on promotion
53eae167
RB
203 * @param $comparison Comparison operator (>, =, ...)
204 * @param $grade Formation on which to restrict, UserFilter::DISPLAY for "any formation"
5d2e55c7 205 * @param $promo Promotion on which the filter is based
53eae167 206 */
a087cc8d
FB
207class UFC_Promo implements UserFilterCondition
208{
a087cc8d
FB
209
210 private $grade;
211 private $promo;
212 private $comparison;
213
214 public function __construct($comparison, $grade, $promo)
215 {
216 $this->grade = $grade;
217 $this->comparison = $comparison;
218 $this->promo = $promo;
38c6fe96
FB
219 if ($this->grade != UserFilter::DISPLAY) {
220 UserFilter::assertGrade($this->grade);
221 }
a087cc8d
FB
222 }
223
784745ce 224 public function buildCondition(UserFilter &$uf)
a087cc8d 225 {
38c6fe96
FB
226 if ($this->grade == UserFilter::DISPLAY) {
227 $sub = $uf->addDisplayFilter();
228 return XDB::format('pd' . $sub . '.promo = {?}', $this->promo);
229 } else {
230 $sub = $uf->addEducationFilter(true, $this->grade);
231 $field = 'pe' . $sub . '.' . UserFilter::promoYear($this->grade);
232 return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo);
233 }
784745ce
FB
234 }
235}
8363588b 236// }}}
784745ce 237
8363588b 238// {{{ class UFC_Name
53eae167 239/** Filters users based on name
5d2e55c7 240 * @param $type Type of name field on which filtering is done (firstname, lastname...)
53eae167 241 * @param $text Text on which to filter
5d2e55c7 242 * @param $mode Flag indicating search type (prefix, suffix, with particule...)
53eae167 243 */
784745ce
FB
244class UFC_Name implements UserFilterCondition
245{
246 const PREFIX = 1;
247 const SUFFIX = 2;
248 const PARTICLE = 7;
249 const VARIANTS = 8;
250 const CONTAINS = 3;
251
252 private $type;
253 private $text;
254 private $mode;
255
256 public function __construct($type, $text, $mode)
257 {
258 $this->type = $type;
259 $this->text = $text;
260 $this->mode = $mode;
261 }
262
263 private function buildNameQuery($type, $variant, $where, UserFilter &$uf)
264 {
265 $sub = $uf->addNameFilter($type, $variant);
266 return str_replace('$ME', 'pn' . $sub, $where);
267 }
268
269 public function buildCondition(UserFilter &$uf)
270 {
271 $left = '$ME.name';
272 $op = ' LIKE ';
273 if (($this->mode & self::PARTICLE) == self::PARTICLE) {
274 $left = 'CONCAT($ME.particle, \' \', $ME.name)';
275 }
276 if (($this->mode & self::CONTAINS) == 0) {
277 $right = XDB::format('{?}', $this->text);
278 $op = ' = ';
279 } else if (($this->mode & self::CONTAINS) == self::PREFIX) {
280 $right = XDB::format('CONCAT({?}, \'%\')', $this->text);
281 } else if (($this->mode & self::CONTAINS) == self::SUFFIX) {
282 $right = XDB::format('CONCAT(\'%\', {?})', $this->text);
283 } else {
284 $right = XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->text);
285 }
286 $cond = $left . $op . $right;
287 $conds = array($this->buildNameQuery($this->type, null, $cond, $uf));
d865c296 288 if (($this->mode & self::VARIANTS) != 0 && isset(UserFilter::$name_variants[$this->type])) {
784745ce
FB
289 foreach (UserFilter::$name_variants[$this->type] as $var) {
290 $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf);
291 }
292 }
293 return implode(' OR ', $conds);
a087cc8d
FB
294 }
295}
8363588b 296// }}}
a087cc8d 297
8363588b 298// {{{ class UFC_Dead
53eae167
RB
299/** Filters users based on death date
300 * @param $comparison Comparison operator
301 * @param $date Date to which death date should be compared
302 */
4927ee54
FB
303class UFC_Dead implements UserFilterCondition
304{
38c6fe96
FB
305 private $comparison;
306 private $date;
307
308 public function __construct($comparison = null, $date = null)
4927ee54 309 {
38c6fe96
FB
310 $this->comparison = $comparison;
311 $this->date = $date;
4927ee54
FB
312 }
313
314 public function buildCondition(UserFilter &$uf)
315 {
38c6fe96
FB
316 $str = 'p.deathdate IS NOT NULL';
317 if (!is_null($this->comparison)) {
318 $str .= ' AND p.deathdate ' . $this->comparison . ' ' . XDB::format('{?}', date('Y-m-d', $this->date));
4927ee54 319 }
38c6fe96 320 return $str;
4927ee54
FB
321 }
322}
8363588b 323// }}}
4927ee54 324
8363588b 325// {{{ class UFC_Registered
53eae167
RB
326/** Filters users based on registration state
327 * @param $active Whether we want to use only "active" users (i.e with a valid redirection)
328 * @param $comparison Comparison operator
329 * @param $date Date to which users registration date should be compared
330 */
4927ee54
FB
331class UFC_Registered implements UserFilterCondition
332{
333 private $active;
38c6fe96
FB
334 private $comparison;
335 private $date;
336
337 public function __construct($active = false, $comparison = null, $date = null)
4927ee54
FB
338 {
339 $this->only_active = $active;
38c6fe96
FB
340 $this->comparison = $comparison;
341 $this->date = $date;
4927ee54
FB
342 }
343
344 public function buildCondition(UserFilter &$uf)
345 {
346 if ($this->active) {
38c6fe96 347 $date = 'a.uid IS NOT NULL AND a.state = \'active\'';
4927ee54 348 } else {
38c6fe96
FB
349 $date = 'a.uid IS NOT NULL AND a.state != \'pending\'';
350 }
351 if (!is_null($this->comparison)) {
352 $date .= ' AND a.registration_date ' . $this->comparison . ' ' . XDB::format('{?}', date('Y-m-d', $this->date));
4927ee54 353 }
38c6fe96 354 return $date;
4927ee54
FB
355 }
356}
8363588b 357// }}}
4927ee54 358
8363588b 359// {{{ class UFC_ProfileUpdated
53eae167
RB
360/** Filters users based on profile update date
361 * @param $comparison Comparison operator
362 * @param $date Date to which profile update date must be compared
363 */
7e735012
FB
364class UFC_ProfileUpdated implements UserFilterCondition
365{
366 private $comparison;
367 private $date;
368
369 public function __construct($comparison = null, $date = null)
370 {
371 $this->comparison = $comparison;
372 $this->date = $date;
373 }
374
375 public function buildCondition(UserFilter &$uf)
376 {
377 return 'p.last_change ' . $this->comparison . XDB::format(' {?}', date('Y-m-d H:i:s', $this->date));
378 }
379}
8363588b 380// }}}
7e735012 381
8363588b 382// {{{ class UFC_Birthday
53eae167
RB
383/** Filters users based on next birthday date
384 * @param $comparison Comparison operator
385 * @param $date Date to which users next birthday date should be compared
386 */
7e735012
FB
387class UFC_Birthday implements UserFilterCondition
388{
389 private $comparison;
390 private $date;
391
392 public function __construct($comparison = null, $date = null)
393 {
394 $this->comparison = $comparison;
395 $this->date = $date;
396 }
397
398 public function buildCondition(UserFilter &$uf)
399 {
400 return 'p.next_birthday ' . $this->comparison . XDB::format(' {?}', date('Y-m-d', $this->date));
401 }
402}
8363588b 403// }}}
7e735012 404
8363588b 405// {{{ class UFC_Sex
53eae167
RB
406/** Filters users based on sex
407 * @parm $sex One of User::GENDER_MALE or User::GENDER_FEMALE, for selecting users
408 */
4927ee54
FB
409class UFC_Sex implements UserFilterCondition
410{
411 private $sex;
412 public function __construct($sex)
413 {
414 $this->sex = $sex;
415 }
416
417 public function buildCondition(UserFilter &$uf)
418 {
419 if ($this->sex != User::GENDER_MALE && $this->sex != User::GENDER_FEMALE) {
420 return self::COND_FALSE;
421 } else {
24e08e33 422 return XDB::format('p.sex = {?}', $this->sex == User::GENDER_FEMALE ? 'female' : 'male');
4927ee54
FB
423 }
424 }
425}
8363588b 426// }}}
4927ee54 427
8363588b 428// {{{ class UFC_Group
53eae167 429/** Filters users based on group membership
5d2e55c7
RB
430 * @param $group Group whose members we are selecting
431 * @param $anim Whether to restrict selection to animators of that group
53eae167 432 */
4927ee54
FB
433class UFC_Group implements UserFilterCondition
434{
435 private $group;
5d2e55c7
RB
436 private $anim;
437 public function __construct($group, $anim = false)
4927ee54
FB
438 {
439 $this->group = $group;
5d2e55c7 440 $this->anim = $anim;
4927ee54
FB
441 }
442
443 public function buildCondition(UserFilter &$uf)
444 {
445 $sub = $uf->addGroupFilter($this->group);
446 $where = 'gpm' . $sub . '.perms IS NOT NULL';
5d2e55c7 447 if ($this->anim) {
4927ee54
FB
448 $where .= ' AND gpm' . $sub . '.perms = \'admin\'';
449 }
450 return $where;
451 }
452}
8363588b 453// }}}
4927ee54 454
8363588b 455// {{{ class UFC_Email
53eae167
RB
456/** Filters users based on email address
457 * @param $email Email whose owner we are looking for
458 */
aa21c568
FB
459class UFC_Email implements UserFilterCondition
460{
461 private $email;
462 public function __construct($email)
463 {
464 $this->email = $email;
465 }
466
467 public function buildCondition(UserFilter &$uf)
468 {
469 if (User::isForeignEmailAddress($this->email)) {
470 $sub = $uf->addEmailRedirectFilter($this->email);
471 return XDB::format('e' . $sub . '.email IS NOT NULL OR a.email = {?}', $this->email);
21401768
FB
472 } else if (User::isVirtualEmailAddress($this->email)) {
473 $sub = $uf->addVirtualEmailFilter($this->email);
474 return 'vr' . $sub . '.redirect IS NOT NULL';
aa21c568 475 } else {
21401768
FB
476 @list($user, $domain) = explode('@', $this->email);
477 $sub = $uf->addAliasFilter($user);
aa21c568
FB
478 return 'al' . $sub . '.alias IS NOT NULL';
479 }
480 }
481}
8363588b 482// }}}
d865c296 483
8363588b 484// {{{ class UFC_EmailList
5d2e55c7 485/** Filters users based on an email list
53eae167
RB
486 * @param $emails List of emails whose owner must be selected
487 */
21401768
FB
488class UFC_EmailList implements UserFilterCondition
489{
490 private $emails;
491 public function __construct($emails)
492 {
493 $this->emails = $emails;
494 }
495
496 public function buildCondition(UserFilter &$uf)
497 {
498 $email = null;
499 $virtual = null;
500 $alias = null;
501 $cond = array();
502
503 if (count($this->emails) == 0) {
504 return UserFilterCondition::COND_TRUE;
505 }
506
507 foreach ($this->emails as $entry) {
508 if (User::isForeignEmailAddress($entry)) {
509 if (is_null($email)) {
510 $email = $uf->addEmailRedirectFilter();
511 }
512 $cond[] = XDB::format('e' . $email . '.email = {?} OR a.email = {?}', $entry, $entry);
513 } else if (User::isVirtualEmailAddress($entry)) {
514 if (is_null($virtual)) {
515 $virtual = $uf->addVirtualEmailFilter();
516 }
517 $cond[] = XDB::format('vr' . $virtual . '.redirect IS NOT NULL AND v' . $virtual . '.alias = {?}', $entry);
518 } else {
519 if (is_null($alias)) {
520 $alias = $uf->addAliasFilter();
521 }
522 @list($user, $domain) = explode('@', $entry);
523 $cond[] = XDB::format('al' . $alias . '.alias = {?}', $user);
524 }
525 }
526 return '(' . implode(') OR (', $cond) . ')';
527 }
528}
8363588b 529// }}}
d865c296 530
8363588b 531// {{{ class UFC_Address
c4b24511
RB
532/** Filters users based on their address
533 * @param $field Field of the address used for filtering (city, street, ...)
534 * @param $text Text for filter
535 * @param $mode Mode for search (PREFIX, SUFFIX, ...)
536 */
537class UFC_Address implements UserFilterCondition
538{
539 const PREFIX = 1;
540 const SUFFIX = 2;
541 const CONTAINS = 3;
542
543 private $field;
544 private $text;
545 private $mode;
546
547 public function __construct($field, $text, $mode)
548 {
549 $this->field = $field;
550 $this->text = $text;
551 $this->mode = $mode;
552 }
553
554 public function buildCondition(UserFilter &$uf)
555 {
556 $left = 'pa.' . $field;
557 $op = ' LIKE ';
558 if (($this->mode & self::CONTAINS) == 0) {
559 $right = XDB::format('{?}', $this->text);
560 $op = ' = ';
561 } else if (($this->mode & self::CONTAINS) == self::PREFIX) {
562 $right = XDB::format('CONCAT({?}, \'%\')', $this->text);
563 } else if (($this->mode & self::CONTAINS) == self::SUFFIX) {
564 $right = XDB::format('CONCAT(\'%\', {?})', $this->text);
565 } else {
566 $right = XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->text);
567 }
568 $cond = $left . $op . $right;
569 $uf->addAddressFilter();
570 return $cond;
571 }
572}
8363588b 573// }}}
c4b24511 574
8363588b 575// {{{ class UFC_Corps
4083b126
RB
576/** Filters users based on the corps they belong to
577 * @param $corps Corps we are looking for (abbreviation)
578 * @param $type Whether we search for original or current corps
579 */
580class UFC_Corps implements UserFilterCondition
581{
5d2e55c7
RB
582 const CURRENT = 1;
583 const ORIGIN = 2;
4083b126
RB
584
585 private $corps;
586 private $type;
587
588 public function __construct($corps, $type = self::CURRENT)
589 {
590 $this->corps = $corps;
591 $this->type = $type;
592 }
593
594 public function buildCondition(UserFilter &$uf)
595 {
5d2e55c7
RB
596 /** Tables shortcuts:
597 * pc for profile_corps,
4083b126
RB
598 * pceo for profile_corps_enum - orginal
599 * pcec for profile_corps_enum - current
600 */
601 $sub = $uf->addCorpsFilter($this->type);
602 $cond = $sub . '.abbreviation = ' . $corps;
603 return $cond;
604 }
605}
8363588b 606// }}}
4083b126 607
8363588b 608// {{{ class UFC_Corps_Rank
4083b126
RB
609/** Filters users based on their rank in the corps
610 * @param $rank Rank we are looking for (abbreviation)
611 */
612class UFC_Corps_Rank implements UserFilterCondition
613{
614 private $rank;
615 public function __construct($rank)
616 {
617 $this->rank = $rank;
618 }
619
620 public function buildCondition(UserFilter &$uf)
621 {
5d2e55c7 622 /** Tables shortcuts:
4083b126
RB
623 * pcr for profile_corps_rank
624 */
625 $sub = $uf->addCorpsRankFilter();
626 $cond = $sub . '.abbreviation = ' . $rank;
627 return $cond;
628 }
629}
8363588b 630// }}}
4083b126 631
6a99c3ac
RB
632// {{{ class UFC_Job_Company
633/** Filters users based on the company they belong to
634 * @param $type The field being searched (self::JOBID, self::JOBNAME or self::JOBACRONYM)
635 * @param $value The searched value
636 */
4e2f2ad2 637class UFC_Job_Company implements UserFilterCondition
6a99c3ac
RB
638{
639 const JOBID = 'id';
640 const JOBNAME = 'name';
641 const JOBACRONYM = 'acronym';
642
643 private $type;
644 private $value;
645
646 public function __construct($type, $value)
647 {
648 $this->assertType($type);
649 $this->type = $type;
650 $this->value = $value;
651 }
652
653 private function assertType($type)
654 {
655 if ($type != self::JOBID && $type != self::JOBNAME && $type != self::JOBACRONYM) {
5d2e55c7 656 Platal::page()->killError("Type de recherche non valide.");
6a99c3ac
RB
657 }
658 }
659
660 public function buildCondition(UserFilter &$uf)
661 {
662 $sub = $uf->addJobCompanyFilter();
663 $cond = $sub . '.' . $this->type . ' = ' . XDB::format('{?}', $this->value);
664 return $cond;
665 }
666}
667// }}}
668
669// {{{ class UFC_Job_Sectorization
670/** Filters users based on the ((sub)sub)sector they work in
671 * @param $sector The sector searched
672 * @param $subsector The subsector
673 * @param $subsubsector The subsubsector
674 */
4e2f2ad2 675class UFC_Job_Sectorization implements UserFilterCondition
6a99c3ac
RB
676{
677
678 private $sector;
679 private $subsector;
680 private $subsubsector;
681
682 public function __construct($sector = null, $subsector = null, $subsubsector = null)
683 {
684 $this->sector = $sector;
685 $this->subsector = $subsector;
686 $this->subsubsector = $subsubsector;
687 }
688
689 public function buildCondition(UserFilter &$uf)
690 {
691 // No need to add the JobFilter, it will be done by addJobSectorizationFilter
692 $conds = array();
693 if ($this->sector !== null) {
694 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SECTOR);
695 $conds[] = $sub . '.id = ' . XDB::format('{?}', $this->sector);
696 }
697 if ($this->subsector !== null) {
698 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSECTOR);
699 $conds[] = $sub . '.id = ' . XDB::format('{?}', $this->subsector);
700 }
701 if ($this->subsubsector !== null) {
702 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSUBSECTOR);
703 $conds[] = $sub . '.id = ' . XDB::format('{?}', $this->subsubsector);
704 }
705 return implode(' AND ', $conds);
706 }
707}
708// }}}
709
710// {{{ class UFC_Job_Description
711/** Filters users based on their job description
712 * @param $description The text being searched for
713 * @param $fields The fields to search for (user-defined, ((sub|)sub|)sector)
714 */
4e2f2ad2 715class UFC_Job_Description implements UserFilterCondition
6a99c3ac
RB
716{
717
718 /** Meta-filters
719 * Built with binary OR on UserFilter::JOB_*
720 */
721 const ANY = 31;
722 const SECTORIZATION = 15;
723
724 private $description;
725 private $fields;
726
727 public function __construct($description)
728 {
729 $this->fields = $fields;
730 $this->description = $description;
731 }
732
733 public function buildCondition(UserFilter &$uf)
734 {
735 $conds = array();
736 if ($this->fields & UserFilter::JOB_USERDEFINED) {
737 $sub = $uf->addJobFilter();
738 $conds[] = $sub . '.description LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->description);
739 }
740 if ($this->fields & UserFilter::JOB_SECTOR) {
741 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SECTOR);
742 $conds[] = $sub . '.name LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->description);
743 }
744 if ($this->fields & UserFilter::JOB_SUBSECTOR) {
745 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSECTOR);
746 $conds[] = $sub . '.name LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->description);
747 }
748 if ($this->fields & UserFilter::JOB_SUBSUBSECTOR) {
749 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_SUBSUBSECTOR);
750 $conds[] = $sub . '.name LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->description);
751 $sub = $uf->addJobSectorizationFilter(UserFilter::JOB_ALTERNATES);
752 $conds[] = $sub . '.name LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->description);
753 }
754 return implode(' OR ', $conds);
755 }
756}
757// }}}
758
0a2e9c74
RB
759// {{{ class UFC_Networking
760/** Filters users based on network identity (IRC, ...)
761 * @param $type Type of network (-1 for any)
762 * @param $value Value to search
763 */
4e2f2ad2 764class UFC_Networking implements UserFilterCondition
0a2e9c74
RB
765{
766 private $type;
767 private $value;
768
769 public function __construct($type, $value)
770 {
771 $this->type = $type;
772 $this->value = $value;
773 }
774
775 public function buildCondition(UserFilter &$uf)
776 {
777 $sub = $uf->addNetworkingFilter();
778 $conds = array();
779 $conds[] = $sub . '.address = ' . XDB::format('CONCAT(\'%\', {?}, \'%\')', $this->value);
780 if ($this->type != -1) {
781 $conds[] = $sub . '.network_type = ' . XDB::format('{?}', $this->type);
782 }
783 return implode(' AND ', $conds);
784 }
785}
786// }}}
787
6d62969e
RB
788// {{{ class UFC_Phone
789/** Filters users based on their phone number
790 * @param $num_type Type of number (pro/user/home)
791 * @param $phone_type Type of phone (fixed/mobile/fax)
792 * @param $number Phone number
793 */
4e2f2ad2 794class UFC_Phone implements UserFilterCondition
6d62969e
RB
795{
796 const NUM_PRO = 'pro';
797 const NUM_USER = 'user';
798 const NUM_HOME = 'address';
799 const NUM_ANY = 'any';
800
801 const PHONE_FIXED = 'fixed';
802 const PHONE_MOBILE = 'mobile';
803 const PHONE_FAX = 'fax';
804 const PHONE_ANY = 'any';
805
806 private $num_type;
807 private $phone_type;
808 private $number;
809
810 public function __construct($number, $num_type = self::NUM_ANY, $phone_type = self::PHONE_ANY)
811 {
812 require_once('profil.inc.php');
813 $this->number = $number;
814 $this->num_type = $num_type;
815 $this->phone_type = format_phone_number($phone_type);
816 }
817
818 public function buildCondition(UserFilter &$uf)
819 {
820 $sub = $uf->addPhoneFilter();
821 $conds = array();
822 $conds[] = $sub . '.search_tel = ' . XDB::format('{?}', $this->number);
823 if ($this->num_type != self::NUM_ANY) {
824 $conds[] = $sub . '.link_type = ' . XDB::format('{?}', $this->num_type);
825 }
826 if ($this->phone_type != self::PHONE_ANY) {
827 $conds[] = $sub . '.tel_type = ' . XDB::format('{?}', $this->phone_type);
828 }
829 return implode(' AND ', $conds);
830 }
831}
832// }}}
833
ceb512d2
RB
834// {{{ class UFC_Medal
835/** Filters users based on their medals
836 * @param $medal ID of the medal
837 * @param $grade Grade of the medal (null for 'any')
838 */
4e2f2ad2 839class UFC_Medal implements UserFilterCondition
ceb512d2
RB
840{
841 private $medal;
842 private $grade;
843
844 public function __construct($medal, $grade = null)
845 {
846 $this->medal = $medal;
847 $this->grade = $grade;
848 }
849
850 public function buildCondition(UserFilter &$uf)
851 {
852 $conds = array();
853 $sub = $uf->addMedalFilter();
854 $conds[] = $sub . '.mid = ' . XDB::format('{?}', $this->medal);
855 if ($this->grade != null) {
856 $conds[] = $sub . '.gid = ' . XDB::format('{?}', $this->grade);
857 }
858 return implode(' AND ', $conds);
859 }
860}
861// }}}
862
671b7073
RB
863// {{{ class UFC_Mentor_Expertise
864/** Filters users by mentoring expertise
865 * @param $expertise Domain of expertise
866 */
4e2f2ad2 867class UFC_Mentor_Expertise implements UserFilterCondition
671b7073
RB
868{
869 private $expertise;
870
871 public function __construct($expertise)
872 {
873 $this->expertise = $expertise;
874 }
875
876 public function buildCondition(UserFilter &$uf)
877 {
878 $sub = $uf->addMentorFilter(UserFilter::MENTOR_EXPERTISE);
879 return $sub . '.expertise LIKE ' . XDB::format('CONCAT(\'%\', {?}, \'%\'', $this->expertise);
880 }
881}
882// }}}
883
884// {{{ class UFC_Mentor_Country
885/** Filters users by mentoring country
886 * @param $country Two-letters code of country being searched
887 */
4e2f2ad2 888class UFC_Mentor_Country implements UserFilterCondition
671b7073
RB
889{
890 private $country;
891
892 public function __construct($country)
893 {
894 $this->country = $country;
895 }
896
897 public function buildCondition(UserFilter &$uf)
898 {
899 $sub = $uf->addMentorFilter(UserFilter::MENTOR_COUNTRY);
900 return $sub . '.country = ' . XDB::format('{?}', $this->country);
901 }
902}
903// }}}
904
905// {{{ class UFC_Mentor_Sectorization
906/** Filters users based on mentoring (sub|)sector
907 * @param $sector ID of sector
908 * @param $subsector Subsector (null for any)
909 */
4e2f2ad2 910class UFC_Mentor_Sectorization implements UserFilterCondition
671b7073
RB
911{
912 private $sector;
913 private $subsector;
914
915 public function __construct($sector, $subsector = null)
916 {
917 $this->sector = $sector;
918 $this->subsubsector = $subsector;
919 }
920
921 public function buildCondition(UserFilter &$uf)
922 {
923 $sub = $uf->addMentorFilter(UserFilter::MENTOR_SECTOR);
924 $conds = array();
925 $conds[] = $sub . '.sectorid = ' . XDB::format('{?}', $this->sector);
926 if ($this->subsector != null) {
927 $conds[] = $sub . '.subsectorid = ' . XDB::format('{?}', $this->subsector);
928 }
929 return implode(' AND ', $conds);
930 }
931}
932// }}}
933
8363588b 934// {{{ class UFC_UserRelated
5d2e55c7 935/** Filters users based on a relation toward a user
53eae167
RB
936 * @param $user User to which searched users are related
937 */
4e7bf1e0 938abstract class UFC_UserRelated implements UserFilterCondition
3f42a6ad 939{
009b8ab7
FB
940 protected $user;
941 public function __construct(PlUser &$user)
942 {
943 $this->user =& $user;
3f42a6ad 944 }
4e7bf1e0 945}
8363588b 946// }}}
3f42a6ad 947
8363588b 948// {{{ class UFC_Contact
5d2e55c7 949/** Filters users who belong to selected user's contacts
53eae167 950 */
4e7bf1e0
FB
951class UFC_Contact extends UFC_UserRelated
952{
3f42a6ad
FB
953 public function buildCondition(UserFilter &$uf)
954 {
009b8ab7 955 $sub = $uf->addContactFilter($this->user->id());
3f42a6ad
FB
956 return 'c' . $sub . '.contact IS NOT NULL';
957 }
958}
8363588b 959// }}}
3f42a6ad 960
8363588b 961// {{{ class UFC_WatchRegistration
53eae167
RB
962/** Filters users being watched by selected user
963 */
4e7bf1e0
FB
964class UFC_WatchRegistration extends UFC_UserRelated
965{
966 public function buildCondition(UserFilter &$uf)
967 {
009b8ab7
FB
968 if (!$this->user->watch('registration')) {
969 return UserFilterCondition::COND_FALSE;
970 }
971 $uids = $this->user->watchUsers();
972 if (count($uids) == 0) {
973 return UserFilterCondition::COND_FALSE;
974 } else {
07eb5b0e 975 return '$UID IN ' . XDB::formatArray($uids);
009b8ab7 976 }
4e7bf1e0
FB
977 }
978}
8363588b 979// }}}
4e7bf1e0 980
8363588b 981// {{{ class UFC_WatchPromo
53eae167
RB
982/** Filters users belonging to a promo watched by selected user
983 * @param $user Selected user (the one watching promo)
984 * @param $grade Formation the user is watching
985 */
4e7bf1e0
FB
986class UFC_WatchPromo extends UFC_UserRelated
987{
988 private $grade;
009b8ab7 989 public function __construct(PlUser &$user, $grade = UserFilter::GRADE_ING)
4e7bf1e0 990 {
009b8ab7 991 parent::__construct($user);
4e7bf1e0
FB
992 $this->grade = $grade;
993 }
994
995 public function buildCondition(UserFilter &$uf)
996 {
009b8ab7
FB
997 $promos = $this->user->watchPromos();
998 if (count($promos) == 0) {
999 return UserFilterCondition::COND_FALSE;
1000 } else {
1001 $sube = $uf->addEducationFilter(true, $this->grade);
1002 $field = 'pe' . $sube . '.' . UserFilter::promoYear($this->grade);
07eb5b0e 1003 return $field . ' IN ' . XDB::formatArray($promos);
009b8ab7 1004 }
4e7bf1e0
FB
1005 }
1006}
8363588b 1007// }}}
4e7bf1e0 1008
8363588b 1009// {{{ class UFC_WatchContact
53eae167
RB
1010/** Filters users watched by selected user
1011 */
009b8ab7 1012class UFC_WatchContact extends UFC_Contact
4e7bf1e0
FB
1013{
1014 public function buildCondition(UserFilter &$uf)
1015 {
009b8ab7
FB
1016 if (!$this->user->watchContacts()) {
1017 return UserFilterCondition::COND_FALSE;
1018 }
1019 return parent::buildCondition($uf);
4e7bf1e0
FB
1020 }
1021}
8363588b 1022// }}}
4e7bf1e0
FB
1023
1024
d865c296
FB
1025/******************
1026 * ORDERS
1027 ******************/
1028
8363588b 1029// {{{ class UserFilterOrder
2d83cac9
RB
1030/** Base class for ordering results of a query.
1031 * Parameters for the ordering must be given to the constructor ($desc for a
1032 * descending order).
1033 * The getSortTokens function is used to get actual ordering part of the query.
1034 */
d865c296
FB
1035abstract class UserFilterOrder
1036{
1037 protected $desc = false;
009b8ab7
FB
1038 public function __construct($desc = false)
1039 {
1040 $this->desc = $desc;
1041 }
d865c296
FB
1042
1043 public function buildSort(UserFilter &$uf)
1044 {
1045 $sel = $this->getSortTokens($uf);
1046 if (!is_array($sel)) {
1047 $sel = array($sel);
1048 }
1049 if ($this->desc) {
1050 foreach ($sel as $k=>$s) {
1051 $sel[$k] = $s . ' DESC';
1052 }
1053 }
1054 return $sel;
1055 }
1056
2d83cac9
RB
1057 /** This function must return the tokens to use for ordering
1058 * @param &$uf The UserFilter whose results must be ordered
1059 * @return The name of the field to use for ordering results
1060 */
d865c296
FB
1061 abstract protected function getSortTokens(UserFilter &$uf);
1062}
8363588b 1063// }}}
d865c296 1064
8363588b 1065// {{{ class UFO_Promo
5d2e55c7
RB
1066/** Orders users by promotion
1067 * @param $grade Formation whose promotion users should be sorted by (restricts results to users of that formation)
53eae167
RB
1068 * @param $desc Whether sort is descending
1069 */
d865c296
FB
1070class UFO_Promo extends UserFilterOrder
1071{
1072 private $grade;
1073
1074 public function __construct($grade = null, $desc = false)
1075 {
009b8ab7 1076 parent::__construct($desc);
d865c296 1077 $this->grade = $grade;
d865c296
FB
1078 }
1079
1080 protected function getSortTokens(UserFilter &$uf)
1081 {
1082 if (UserFilter::isGrade($this->grade)) {
1083 $sub = $uf->addEducationFilter($this->grade);
1084 return 'pe' . $sub . '.' . UserFilter::promoYear($this->grade);
1085 } else {
1086 $sub = $uf->addDisplayFilter();
1087 return 'pd' . $sub . '.promo';
1088 }
1089 }
1090}
8363588b 1091// }}}
d865c296 1092
8363588b 1093// {{{ class UFO_Name
53eae167 1094/** Sorts users by name
5d2e55c7
RB
1095 * @param $type Type of name on which to sort (firstname...)
1096 * @param $variant Variant of that name to use (marital, ordinary...)
53eae167
RB
1097 * @param $particle Set to true if particles should be included in the sorting order
1098 * @param $desc If sort order should be descending
1099 */
d865c296
FB
1100class UFO_Name extends UserFilterOrder
1101{
1102 private $type;
1103 private $variant;
1104 private $particle;
1105
1106 public function __construct($type, $variant = null, $particle = false, $desc = false)
1107 {
009b8ab7 1108 parent::__construct($desc);
d865c296
FB
1109 $this->type = $type;
1110 $this->variant = $variant;
1111 $this->particle = $particle;
d865c296
FB
1112 }
1113
1114 protected function getSortTokens(UserFilter &$uf)
1115 {
1116 if (UserFilter::isDisplayName($this->type)) {
1117 $sub = $uf->addDisplayFilter();
1118 return 'pd' . $sub . '.' . $this->type;
1119 } else {
1120 $sub = $uf->addNameFilter($this->type, $this->variant);
1121 if ($this->particle) {
1122 return 'CONCAT(pn' . $sub . '.particle, \' \', pn' . $sub . '.name)';
1123 } else {
1124 return 'pn' . $sub . '.name';
1125 }
1126 }
1127 }
1128}
8363588b 1129// }}}
d865c296 1130
8363588b 1131// {{{ class UFO_Registration
53eae167
RB
1132/** Sorts users based on registration date
1133 */
38c6fe96
FB
1134class UFO_Registration extends UserFilterOrder
1135{
009b8ab7 1136 protected function getSortTokens(UserFilter &$uf)
38c6fe96 1137 {
009b8ab7 1138 return 'a.registration_date';
38c6fe96 1139 }
009b8ab7 1140}
8363588b 1141// }}}
38c6fe96 1142
8363588b 1143// {{{ class UFO_Birthday
53eae167
RB
1144/** Sorts users based on next birthday date
1145 */
009b8ab7
FB
1146class UFO_Birthday extends UserFilterOrder
1147{
38c6fe96
FB
1148 protected function getSortTokens(UserFilter &$uf)
1149 {
009b8ab7 1150 return 'p.next_birthday';
38c6fe96
FB
1151 }
1152}
8363588b 1153// }}}
38c6fe96 1154
8363588b 1155// {{{ class UFO_ProfileUpdate
53eae167
RB
1156/** Sorts users based on last profile update
1157 */
009b8ab7
FB
1158class UFO_ProfileUpdate extends UserFilterOrder
1159{
1160 protected function getSortTokens(UserFilter &$uf)
1161 {
1162 return 'p.last_change';
1163 }
1164}
8363588b 1165// }}}
009b8ab7 1166
8363588b 1167// {{{ class UFO_Death
53eae167
RB
1168/** Sorts users based on death date
1169 */
009b8ab7
FB
1170class UFO_Death extends UserFilterOrder
1171{
1172 protected function getSortTokens(UserFilter &$uf)
1173 {
1174 return 'p.deathdate';
1175 }
1176}
8363588b 1177// }}}
009b8ab7
FB
1178
1179
d865c296
FB
1180/***********************************
1181 *********************************
1182 USER FILTER CLASS
1183 *********************************
1184 ***********************************/
1185
8363588b 1186// {{{ class UserFilter
2d83cac9
RB
1187/** This class provides a convenient and centralized way of filtering users.
1188 *
1189 * Usage:
1190 * $uf = new UserFilter(new UFC_Blah($x, $y), new UFO_Coin($z, $t));
1191 *
1192 * Resulting UserFilter can be used to:
1193 * - get a list of User objects matching the filter
1194 * - get a list of UIDs matching the filter
1195 * - get the number of users matching the filter
1196 * - check whether a given User matches the filter
1197 * - filter a list of User objects depending on whether they match the filter
1198 *
1199 * Usage for UFC and UFO objects:
1200 * A UserFilter will call all private functions named XXXJoins.
1201 * These functions must return an array containing the list of join
1202 * required by the various UFC and UFO associated to the UserFilter.
1203 * Entries in those returned array are of the following form:
1204 * 'join_tablealias' => array('join_type', 'joined_table', 'join_criter')
1205 * which will be translated into :
1206 * join_type JOIN joined_table AS join_tablealias ON (join_criter)
1207 * in the final query.
1208 *
1209 * In the join_criter text, $ME is replaced with 'join_tablealias', $PID with
1210 * profile.pid, and $UID with auth_user_md5.user_id.
1211 *
1212 * For each kind of "JOIN" needed, a function named addXXXFilter() should be defined;
1213 * its parameter will be used to set various private vars of the UserFilter describing
1214 * the required joins ; such a function shall return the "join_tablealias" to use
1215 * when referring to the joined table.
1216 *
1217 * For example, if data from profile_job must be available to filter results,
1218 * the UFC object will call $uf-addJobFilter(), which will set the 'with_pj' var and
1219 * return 'pj', the short name to use when referring to profile_job; when building
1220 * the query, calling the jobJoins function will return an array containing a single
1221 * row:
1222 * 'pj' => array('left', 'profile_job', '$ME.pid = $UID');
1223 *
1224 * The 'register_optional' function can be used to generate unique table aliases when
1225 * the same table has to be joined several times with different aliases.
1226 */
a087cc8d
FB
1227class UserFilter
1228{
d865c296
FB
1229 static private $joinMethods = array();
1230
a087cc8d 1231 private $root;
24e08e33 1232 private $sort = array();
784745ce 1233 private $query = null;
24e08e33 1234 private $orderby = null;
784745ce 1235
aa21c568 1236 private $lastcount = null;
d865c296 1237
24e08e33 1238 public function __construct($cond = null, $sort = null)
5dd9d823 1239 {
d865c296
FB
1240 if (empty(self::$joinMethods)) {
1241 $class = new ReflectionClass('UserFilter');
1242 foreach ($class->getMethods() as $method) {
1243 $name = $method->getName();
1244 if (substr($name, -5) == 'Joins' && $name != 'buildJoins') {
1245 self::$joinMethods[] = $name;
1246 }
1247 }
1248 }
5dd9d823
FB
1249 if (!is_null($cond)) {
1250 if ($cond instanceof UserFilterCondition) {
1251 $this->setCondition($cond);
1252 }
1253 }
24e08e33
FB
1254 if (!is_null($sort)) {
1255 if ($sort instanceof UserFilterOrder) {
1256 $this->addSort($sort);
d865c296
FB
1257 } else if (is_array($sort)) {
1258 foreach ($sort as $s) {
1259 $this->addSort($s);
1260 }
24e08e33
FB
1261 }
1262 }
5dd9d823
FB
1263 }
1264
784745ce
FB
1265 private function buildQuery()
1266 {
d865c296
FB
1267 if (is_null($this->orderby)) {
1268 $orders = array();
1269 foreach ($this->sort as $sort) {
1270 $orders = array_merge($orders, $sort->buildSort($this));
1271 }
1272 if (count($orders) == 0) {
1273 $this->orderby = '';
1274 } else {
1275 $this->orderby = 'ORDER BY ' . implode(', ', $orders);
1276 }
1277 }
784745ce
FB
1278 if (is_null($this->query)) {
1279 $where = $this->root->buildCondition($this);
1280 $joins = $this->buildJoins();
1281 $this->query = 'FROM accounts AS a
eb1449b8
FB
1282 LEFT JOIN account_profiles AS ap ON (ap.uid = a.uid AND FIND_IN_SET(\'owner\', ap.perms))
1283 LEFT JOIN profiles AS p ON (p.pid = ap.pid)
784745ce
FB
1284 ' . $joins . '
1285 WHERE (' . $where . ')';
1286 }
1287 }
1288
1289 private function formatJoin(array $joins)
1290 {
1291 $str = '';
1292 foreach ($joins as $key => $infos) {
1293 $mode = $infos[0];
1294 $table = $infos[1];
1295 if ($mode == 'inner') {
1296 $str .= 'INNER JOIN ';
1297 } else if ($mode == 'left') {
1298 $str .= 'LEFT JOIN ';
1299 } else {
1300 Platal::page()->kill("Join mode error");
1301 }
1302 $str .= $table . ' AS ' . $key;
1303 if (isset($infos[2])) {
4927ee54 1304 $str .= ' ON (' . str_replace(array('$ME', '$PID', '$UID'), array($key, 'p.pid', 'a.uid'), $infos[2]) . ')';
784745ce
FB
1305 }
1306 $str .= "\n";
1307 }
1308 return $str;
1309 }
1310
1311 private function buildJoins()
1312 {
d865c296
FB
1313 $joins = array();
1314 foreach (self::$joinMethods as $method) {
1315 $joins = array_merge($joins, $this->$method());
1316 }
784745ce
FB
1317 return $this->formatJoin($joins);
1318 }
a087cc8d 1319
d865c296
FB
1320 private function getUIDList($uids = null, $count = null, $offset = null)
1321 {
1322 $this->buildQuery();
1323 $limit = '';
1324 if (!is_null($count)) {
1325 if (!is_null($offset)) {
76cbe885 1326 $limit = XDB::format('LIMIT {?}, {?}', (int)$offset, (int)$count);
d865c296 1327 } else {
76cbe885 1328 $limit = XDB::format('LIMIT {?}', (int)$count);
d865c296
FB
1329 }
1330 }
1331 $cond = '';
1332 if (!is_null($uids)) {
07eb5b0e 1333 $cond = ' AND a.uid IN ' . XDB::formatArray($uids);
d865c296
FB
1334 }
1335 $fetched = XDB::fetchColumn('SELECT SQL_CALC_FOUND_ROWS a.uid
1336 ' . $this->query . $cond . '
1337 GROUP BY a.uid
1338 ' . $this->orderby . '
1339 ' . $limit);
1340 $this->lastcount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()');
1341 return $fetched;
1342 }
1343
a087cc8d
FB
1344 /** Check that the user match the given rule.
1345 */
1346 public function checkUser(PlUser &$user)
1347 {
784745ce
FB
1348 $this->buildQuery();
1349 $count = (int)XDB::fetchOneCell('SELECT COUNT(*)
1350 ' . $this->query . XDB::format(' AND a.uid = {?}', $user->id()));
1351 return $count == 1;
a087cc8d
FB
1352 }
1353
1354 /** Filter a list of user to extract the users matching the rule.
1355 */
d865c296 1356 public function filter(array $users, $count = null, $offset = null)
a087cc8d 1357 {
4927ee54
FB
1358 $this->buildQuery();
1359 $table = array();
1360 $uids = array();
1361 foreach ($users as $user) {
07eb5b0e
FB
1362 if ($user instanceof PlUser) {
1363 $uid = $user->id();
1364 } else {
1365 $uid = $user;
1366 }
1367 $uids[] = $uid;
1368 $table[$uid] = $user;
4927ee54 1369 }
d865c296 1370 $fetched = $this->getUIDList($uids, $count, $offset);
a087cc8d 1371 $output = array();
4927ee54
FB
1372 foreach ($fetched as $uid) {
1373 $output[] = $table[$uid];
a087cc8d
FB
1374 }
1375 return $output;
1376 }
1377
d865c296 1378 public function getUIDs($count = null, $offset = null)
4927ee54 1379 {
d865c296
FB
1380 return $this->getUIDList(null, $count, $offset);
1381 }
1382
1383 public function getUsers($count = null, $offset = null)
1384 {
1385 return User::getBulkUsersWithUIDs($this->getUIDs($count, $offset));
4927ee54
FB
1386 }
1387
d865c296 1388 public function getTotalCount()
4927ee54 1389 {
38c6fe96 1390 if (is_null($this->lastcount)) {
aa21c568 1391 $this->buildQuery();
7e735012
FB
1392 return (int)XDB::fetchOneCell('SELECT COUNT(DISTINCT a.uid)
1393 ' . $this->query);
38c6fe96
FB
1394 } else {
1395 return $this->lastcount;
1396 }
4927ee54
FB
1397 }
1398
a087cc8d
FB
1399 public function setCondition(UserFilterCondition &$cond)
1400 {
1401 $this->root =& $cond;
784745ce 1402 $this->query = null;
a087cc8d
FB
1403 }
1404
24e08e33
FB
1405 public function addSort(UserFilterOrder &$sort)
1406 {
d865c296
FB
1407 $this->sort[] = $sort;
1408 $this->orderby = null;
24e08e33
FB
1409 }
1410
a087cc8d
FB
1411 static public function getLegacy($promo_min, $promo_max)
1412 {
a087cc8d 1413 if ($promo_min != 0) {
784745ce 1414 $min = new UFC_Promo('>=', self::GRADE_ING, intval($promo_min));
5dd9d823
FB
1415 } else {
1416 $min = new UFC_True();
a087cc8d 1417 }
a087cc8d 1418 if ($promo_max != 0) {
784745ce 1419 $max = new UFC_Promo('<=', self::GRADE_ING, intval($promo_max));
a087cc8d 1420 } else {
5dd9d823 1421 $max = new UFC_True();
a087cc8d 1422 }
5dd9d823 1423 return new UserFilter(new UFC_And($min, $max));
a087cc8d 1424 }
784745ce 1425
07eb5b0e
FB
1426 static public function sortByName()
1427 {
1428 return array(new UFO_Name(self::LASTNAME), new UFO_Name(self::FIRSTNAME));
1429 }
1430
1431 static public function sortByPromo()
1432 {
1433 return array(new UFO_Promo(), new UFO_Name(self::LASTNAME), new UFO_Name(self::FIRSTNAME));
1434 }
1435
aa21c568
FB
1436 static private function getDBSuffix($string)
1437 {
1438 return preg_replace('/[^a-z0-9]/i', '', $string);
1439 }
1440
1441
2d83cac9
RB
1442 /** Stores a new (and unique) table alias in the &$table table
1443 * @param &$table Array in which the table alias must be stored
1444 * @param $val Value which will then be used to build the join
1445 * @return Name of the newly created alias
1446 */
aa21c568
FB
1447 private $option = 0;
1448 private function register_optional(array &$table, $val)
1449 {
1450 if (is_null($val)) {
1451 $sub = $this->option++;
1452 $index = null;
1453 } else {
1454 $sub = self::getDBSuffix($val);
1455 $index = $val;
1456 }
1457 $sub = '_' . $sub;
1458 $table[$sub] = $index;
1459 return $sub;
1460 }
784745ce 1461
d865c296
FB
1462 /** DISPLAY
1463 */
38c6fe96 1464 const DISPLAY = 'display';
d865c296
FB
1465 private $pd = false;
1466 public function addDisplayFilter()
1467 {
1468 $this->pd = true;
1469 return '';
1470 }
1471
1472 private function displayJoins()
1473 {
1474 if ($this->pd) {
1475 return array('pd' => array('left', 'profile_display', '$ME.pid = $PID'));
1476 } else {
1477 return array();
1478 }
1479 }
1480
784745ce
FB
1481 /** NAMES
1482 */
d865c296 1483 /* name tokens */
784745ce
FB
1484 const LASTNAME = 'lastname';
1485 const FIRSTNAME = 'firstname';
1486 const NICKNAME = 'nickname';
1487 const PSEUDONYM = 'pseudonym';
1488 const NAME = 'name';
d865c296 1489 /* name variants */
784745ce
FB
1490 const VN_MARITAL = 'marital';
1491 const VN_ORDINARY = 'ordinary';
1492 const VN_OTHER = 'other';
1493 const VN_INI = 'ini';
d865c296
FB
1494 /* display names */
1495 const DN_FULL = 'directory_name';
1496 const DN_DISPLAY = 'yourself';
1497 const DN_YOURSELF = 'yourself';
1498 const DN_DIRECTORY = 'directory_name';
1499 const DN_PRIVATE = 'private_name';
1500 const DN_PUBLIC = 'public_name';
1501 const DN_SHORT = 'short_name';
1502 const DN_SORT = 'sort_name';
784745ce
FB
1503
1504 static public $name_variants = array(
1505 self::LASTNAME => array(self::VN_MARITAL, self::VN_ORDINARY),
d865c296
FB
1506 self::FIRSTNAME => array(self::VN_ORDINARY, self::VN_INI, self::VN_OTHER)
1507 );
784745ce
FB
1508
1509 static public function assertName($name)
1510 {
1511 if (!Profile::getNameTypeId($name)) {
1512 Platal::page()->kill('Invalid name type');
1513 }
1514 }
1515
d865c296
FB
1516 static public function isDisplayName($name)
1517 {
1518 return $name == self::DN_FULL || $name == self::DN_DISPLAY
1519 || $name == self::DN_YOURSELF || $name == self::DN_DIRECTORY
1520 || $name == self::DN_PRIVATE || $name == self::DN_PUBLIC
1521 || $name == self::DN_SHORT || $name == self::DN_SORT;
1522 }
1523
784745ce 1524 private $pn = array();
784745ce
FB
1525 public function addNameFilter($type, $variant = null)
1526 {
1527 if (!is_null($variant)) {
1528 $ft = $type . '_' . $variant;
1529 } else {
1530 $ft = $type;
1531 }
1532 $sub = '_' . $ft;
1533 self::assertName($ft);
1534
1535 if (!is_null($variant) && $variant == 'other') {
aa21c568 1536 $sub .= $this->option++;
784745ce
FB
1537 }
1538 $this->pn[$sub] = Profile::getNameTypeId($ft);
1539 return $sub;
1540 }
1541
1542 private function nameJoins()
1543 {
1544 $joins = array();
1545 foreach ($this->pn as $sub => $type) {
1546 $joins['pn' . $sub] = array('left', 'profile_name', '$ME.pid = $PID AND $ME.typeid = ' . $type);
1547 }
1548 return $joins;
1549 }
1550
1551
1552 /** EDUCATION
1553 */
1554 const GRADE_ING = 'Ing.';
1555 const GRADE_PHD = 'PhD';
1556 const GRADE_MST = 'M%';
1557 static public function isGrade($grade)
1558 {
38c6fe96 1559 return $grade == self::GRADE_ING || $grade == self::GRADE_PHD || $grade == self::GRADE_MST;
784745ce
FB
1560 }
1561
1562 static public function assertGrade($grade)
1563 {
1564 if (!self::isGrade($grade)) {
1565 Platal::page()->killError("Diplôme non valide");
1566 }
1567 }
1568
d865c296
FB
1569 static public function promoYear($grade)
1570 {
1571 // XXX: Definition of promotion for phds and masters might change in near future.
1572 return ($grade == UserFilter::GRADE_ING) ? 'entry_year' : 'grad_year';
1573 }
1574
784745ce
FB
1575 private $pepe = array();
1576 private $with_pee = false;
784745ce
FB
1577 public function addEducationFilter($x = false, $grade = null)
1578 {
1579 if (!$x) {
aa21c568
FB
1580 $index = $this->option;
1581 $sub = $this->option++;
784745ce
FB
1582 } else {
1583 self::assertGrade($grade);
1584 $index = $grade;
1585 $sub = $grade[0];
1586 $this->with_pee = true;
1587 }
1588 $sub = '_' . $sub;
1589 $this->pepe[$index] = $sub;
1590 return $sub;
1591 }
1592
1593 private function educationJoins()
1594 {
1595 $joins = array();
1596 if ($this->with_pee) {
1597 $joins['pee'] = array('inner', 'profile_education_enum', 'pee.abbreviation = \'X\'');
1598 }
1599 foreach ($this->pepe as $grade => $sub) {
1600 if ($this->isGrade($grade)) {
1601 $joins['pe' . $sub] = array('left', 'profile_education', '$ME.eduid = pee.id AND $ME.uid = $PID');
1602 $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.abbreviation LIKE ' .
1603 XDB::format('{?}', $grade));
1604 } else {
1605 $joins['pe' . $sub] = array('left', 'profile_education', '$ME.uid = $PID');
1606 $joins['pee' . $sub] = array('inner', 'profile_education_enum', '$ME.id = pe' . $sub . '.eduid');
1607 $joins['pede' . $sub] = array('inner', 'profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid');
1608 }
1609 }
1610 return $joins;
1611 }
4927ee54
FB
1612
1613
1614 /** GROUPS
1615 */
1616 private $gpm = array();
4927ee54
FB
1617 public function addGroupFilter($group = null)
1618 {
1619 if (!is_null($group)) {
1620 if (ctype_digit($group)) {
1621 $index = $sub = $group;
1622 } else {
1623 $index = $group;
aa21c568 1624 $sub = self::getDBSuffix($group);
4927ee54
FB
1625 }
1626 } else {
aa21c568 1627 $sub = 'group_' . $this->option++;
4927ee54
FB
1628 $index = null;
1629 }
1630 $sub = '_' . $sub;
1631 $this->gpm[$sub] = $index;
1632 return $sub;
1633 }
1634
1635 private function groupJoins()
1636 {
1637 $joins = array();
1638 foreach ($this->gpm as $sub => $key) {
1639 if (is_null($key)) {
1640 $joins['gpa' . $sub] = array('inner', 'groupex.asso');
1641 $joins['gpm' . $sub] = array('left', 'groupex.membres', '$ME.uid = $UID AND $ME.asso_id = gpa' . $sub . '.id');
1642 } else if (ctype_digit($key)) {
1643 $joins['gpm' . $sub] = array('left', 'groupex.membres', '$ME.uid = $UID AND $ME.asso_id = ' . $key);
1644 } else {
1645 $joins['gpa' . $sub] = array('inner', 'groupex.asso', XDB::format('$ME.diminutif = {?}', $key));
1646 $joins['gpm' . $sub] = array('left', 'groupex.membres', '$ME.uid = $UID AND $ME.asso_id = gpa' . $sub . '.id');
1647 }
1648 }
1649 return $joins;
1650 }
aa21c568
FB
1651
1652 /** EMAILS
1653 */
1654 private $e = array();
1655 public function addEmailRedirectFilter($email = null)
1656 {
1657 return $this->register_optional($this->e, $email);
1658 }
1659
1660 private $ve = array();
1661 public function addVirtualEmailFilter($email = null)
1662 {
21401768 1663 $this->addAliasFilter(self::ALIAS_FORLIFE);
aa21c568
FB
1664 return $this->register_optional($this->ve, $email);
1665 }
1666
21401768
FB
1667 const ALIAS_BEST = 'bestalias';
1668 const ALIAS_FORLIFE = 'forlife';
aa21c568
FB
1669 private $al = array();
1670 public function addAliasFilter($alias = null)
1671 {
1672 return $this->register_optional($this->al, $alias);
1673 }
1674
1675 private function emailJoins()
1676 {
1677 global $globals;
1678 $joins = array();
1679 foreach ($this->e as $sub=>$key) {
1680 if (is_null($key)) {
1681 $joins['e' . $sub] = array('left', 'emails', '$ME.uid = $UID AND $ME.flags != \'filter\'');
1682 } else {
1683 $joins['e' . $sub] = array('left', 'emails', XDB::format('$ME.uid = $UID AND $ME.flags != \'filter\' AND $ME.email = {?}', $key));
1684 }
1685 }
21401768 1686 foreach ($this->al as $sub=>$key) {
aa21c568 1687 if (is_null($key)) {
21401768
FB
1688 $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\')');
1689 } else if ($key == self::ALIAS_BEST) {
1690 $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND FIND_IN_SET(\'bestalias\', $ME.flags)');
1691 } else if ($key == self::ALIAS_FORLIFE) {
1692 $joins['al' . $sub] = array('left', 'aliases', '$ME.id = $UID AND $ME.type = \'a_vie\'');
aa21c568 1693 } else {
21401768 1694 $joins['al' . $sub] = array('left', 'aliases', XDB::format('$ME.id = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND $ME.alias = {?}', $key));
aa21c568 1695 }
aa21c568 1696 }
21401768 1697 foreach ($this->ve as $sub=>$key) {
aa21c568 1698 if (is_null($key)) {
21401768 1699 $joins['v' . $sub] = array('left', 'virtual', '$ME.type = \'user\'');
aa21c568 1700 } else {
21401768 1701 $joins['v' . $sub] = array('left', 'virtual', XDB::format('$ME.type = \'user\' AND $ME.alias = {?}', $key));
aa21c568 1702 }
21401768
FB
1703 $joins['vr' . $sub] = array('left', 'virtual_redirect', XDB::format('$ME.vid = v' . $sub . '.vid
1704 AND ($ME.redirect IN (CONCAT(al_forlife.alias, \'@\', {?}),
1705 CONCAT(al_forlife.alias, \'@\', {?}),
1706 a.email))',
1707 $globals->mail->domain, $globals->mail->domain2));
aa21c568
FB
1708 }
1709 return $joins;
1710 }
3f42a6ad
FB
1711
1712
c4b24511
RB
1713 /** ADDRESSES
1714 */
1715 private $pa = false;
1716 public function addAddressFilter()
1717 {
1718 $this->pa = true;
1719 }
1720
1721 private function addressJoins()
1722 {
1723 $joins = array();
1724 if ($this->pa) {
5d2e55c7 1725 $joins['pa'] = array('left', 'profile_address', '$ME.pid = $PID');
c4b24511
RB
1726 }
1727 return $joins;
1728 }
1729
1730
4083b126
RB
1731 /** CORPS
1732 */
1733
1734 private $pc = false;
1735 private $pce = array();
1736 private $pcr = false;
1737 public function addCorpsFilter($type)
1738 {
1739 $this->pc = true;
1740 if ($type == UFC_Corps::CURRENT) {
1741 $pce['pcec'] = 'current_corpsid';
1742 return 'pcec';
1743 } else if ($type == UFC_Corps::ORIGIN) {
1744 $pce['pceo'] = 'original_corpsid';
1745 return 'pceo';
1746 }
1747 }
1748
1749 public function addCorpsRankFilter()
1750 {
1751 $this->pc = true;
1752 $this->pcr = true;
1753 return 'pcr';
1754 }
1755
1756 private function corpsJoins()
1757 {
1758 $joins = array();
1759 if ($this->pc) {
1760 $joins['pc'] = array('left', 'profile_corps', '$ME.uid = $UID');
1761 }
1762 if ($this->pcr) {
1763 $joins['pcr'] = array('left', 'profile_corps_rank_enum', '$ME.id = pc.rankid');
1764 }
1765 foreach($this->pce as $sub => $field) {
1766 $joins[$sub] = array('left', 'profile_corps_enum', '$ME.id = pc.' . $field);
1767 }
1768 return $joins;
1769 }
1770
6a99c3ac
RB
1771 /** JOBS
1772 */
1773
1774 const JOB_SECTOR = 1;
1775 const JOB_SUBSECTOR = 2;
1776 const JOB_SUBSUBSECTOR = 4;
1777 const JOB_ALTERNATES = 8;
1778 const JOB_USERDEFINED = 16;
1779
1780 /** Joins :
1781 * pj => profile_job
1782 * pje => profile_job_enum
1783 * pjse => profile_job_sector_enum
1784 * pjsse => profile_job_subsector_enum
1785 * pjssse => profile_job_subsubsector_enum
1786 * pja => profile_job_alternates
1787 */
1788 private $with_pj = false;
1789 private $with_pje = false;
1790 private $with_pjse = false;
1791 private $with_pjsse = false;
1792 private $with_pjssse = false;
1793 private $with_pja = false;
1794
1795 public function addJobFilter()
1796 {
1797 $this->with_pj = true;
1798 return 'pj';
1799 }
1800
1801 public function addJobCompanyFilter()
1802 {
1803 $this->addJobFilter();
1804 $this->with_pje = true;
1805 return 'pje';
1806 }
1807
1808 public function addJobSectorizationFilter($type)
1809 {
1810 $this->addJobFilter();
1811 if ($type == self::JOB_SECTOR) {
1812 $this->with_pjse = true;
1813 return 'pjse';
1814 } else if ($type == self::JOB_SUBSECTOR) {
1815 $this->with_pjsse = true;
1816 return 'pjsse';
1817 } else if ($type == self::JOB_SUBSUBSECTOR) {
1818 $this->with_pjssse = true;
1819 return 'pjssse';
1820 } else if ($type == self::JOB_ALTERNATES) {
1821 $this->with_pja = true;
1822 return 'pja';
1823 }
1824 }
1825
1826 private function jobJoins()
1827 {
1828 $joins = array();
1829 if ($this->with_pj) {
1830 $joins['pj'] = array('left', 'profile_job', '$ME.uid = $UID');
1831 }
1832 if ($this->with_pje) {
1833 $joins['pje'] = array('left', 'profile_job_enum', '$ME.id = pj.jobid');
1834 }
1835 if ($this->with_pjse) {
1836 $joins['pjse'] = array('left', 'profile_job_sector_enum', '$ME.id = pj.sectorid');
1837 }
1838 if ($this->with_pjsse) {
1839 $joins['pjsse'] = array('left', 'profile_job_subsector_enum', '$ME.id = pj.subsectorid');
1840 }
1841 if ($this->with_pjssse) {
1842 $joins['pjssse'] = array('left', 'profile_job_subsubsector_enum', '$ME.id = pj.subsubsectorid');
1843 }
1844 if ($this->with_pja) {
1845 $joins['pja'] = array('left', 'profile_job_alternates', '$ME.subsubsectorid = pj.subsubsectorid');
1846 }
1847 return $joins;
1848 }
1849
0a2e9c74
RB
1850 /** NETWORKING
1851 */
1852
1853 private $with_pnw = false;
1854 public function addNetworkingFilter()
1855 {
1856 $this->with_pnw = true;
1857 return 'pnw';
1858 }
1859
1860 private function networkingJoins()
1861 {
1862 $joins = array();
1863 if ($this->with_pnw) {
1864 $joins['pnw'] = array('left', 'profile_networking', '$ME.uid = $UID');
1865 }
1866 return $joins;
1867 }
1868
6d62969e
RB
1869 /** PHONE
1870 */
1871
2d83cac9 1872 private $with_ptel = false;
6d62969e
RB
1873
1874 public function addPhoneFilter()
1875 {
2d83cac9 1876 $this->with_ptel = true;
6d62969e
RB
1877 return 'ptel';
1878 }
1879
1880 private function phoneJoins()
1881 {
1882 $joins = array();
2d83cac9 1883 if ($this->with_ptel) {
6d62969e
RB
1884 $joins['ptel'] = array('left', 'profile_phone', '$ME.uid = $UID');
1885 }
1886 return $joins;
1887 }
1888
ceb512d2
RB
1889 /** MEDALS
1890 */
1891
2d83cac9 1892 private $with_pmed = false;
ceb512d2
RB
1893 public function addMedalFilter()
1894 {
2d83cac9 1895 $this->with_pmed = true;
ceb512d2
RB
1896 return 'pmed';
1897 }
1898
1899 private function medalJoins()
1900 {
1901 $joins = array();
2d83cac9 1902 if ($this->with_pmed) {
ceb512d2
RB
1903 $joins['pmed'] = array('left', 'profile_medals_sub', '$ME.uid = $UID');
1904 }
1905 return $joins;
1906 }
1907
671b7073
RB
1908 /** MENTORING
1909 */
1910
1911 private $pms = array();
1912 const MENTOR_EXPERTISE = 1;
1913 const MENTOR_COUNTRY = 2;
1914 const MENTOR_SECTOR = 3;
1915
1916 public function addMentorFilter($type)
1917 {
1918 switch($type) {
1919 case MENTOR_EXPERTISE:
1920 $pms['pme'] = 'profile_mentor';
1921 return 'pme';
1922 case MENTOR_COUNTRY:
1923 $pms['pmc'] = 'profile_mentor_country';
1924 return 'pmc';
1925 case MENTOR_SECTOR:
1926 $pms['pms'] = 'profile_mentor_sector';
1927 return 'pms';
1928 default:
5d2e55c7 1929 Platal::page()->killError("Undefined mentor filter.");
671b7073
RB
1930 }
1931 }
1932
1933 private function mentorJoins()
1934 {
1935 $joins = array();
1936 foreach ($this->pms as $sub => $tab) {
1937 $joins[$sub] = array('left', $tab, '$ME.uid = $UID');
1938 }
1939 return $joins;
1940 }
1941
3f42a6ad
FB
1942 /** CONTACTS
1943 */
1944 private $cts = array();
1945 public function addContactFilter($uid = null)
1946 {
1947 return $this->register_optional($this->cts, is_null($uid) ? null : 'user_' . $uid);
1948 }
1949
1950 private function contactJoins()
1951 {
1952 $joins = array();
1953 foreach ($this->cts as $sub=>$key) {
1954 if (is_null($key)) {
1955 $joins['c' . $sub] = array('left', 'contacts', '$ME.contact = $UID');
1956 } else {
1957 $joins['c' . $sub] = array('left', 'contacts', XDB::format('$ME.uid = {?} AND $ME.contact = $UID', substr($key, 5)));
1958 }
1959 }
1960 return $joins;
1961 }
4e7bf1e0
FB
1962
1963
1964 /** CARNET
1965 */
1966 private $wn = array();
1967 public function addWatchRegistrationFilter($uid = null)
1968 {
1969 return $this->register_optional($this->wn, is_null($uid) ? null : 'user_' . $uid);
1970 }
1971
1972 private $wp = array();
1973 public function addWatchPromoFilter($uid = null)
1974 {
1975 return $this->register_optional($this->wp, is_null($uid) ? null : 'user_' . $uid);
1976 }
1977
1978 private $w = array();
1979 public function addWatchFilter($uid = null)
1980 {
1981 return $this->register_optional($this->w, is_null($uid) ? null : 'user_' . $uid);
1982 }
1983
1984 private function watchJoins()
1985 {
1986 $joins = array();
1987 foreach ($this->w as $sub=>$key) {
1988 if (is_null($key)) {
1989 $joins['w' . $sub] = array('left', 'watch');
1990 } else {
1991 $joins['w' . $sub] = array('left', 'watch', XDB::format('$ME.uid = {?}', substr($key, 5)));
1992 }
1993 }
1994 foreach ($this->wn as $sub=>$key) {
1995 if (is_null($key)) {
1996 $joins['wn' . $sub] = array('left', 'watch_nonins', '$ME.ni_id = $UID');
1997 } else {
1998 $joins['wn' . $sub] = array('left', 'watch_nonins', XDB::format('$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5)));
1999 }
2000 }
2001 foreach ($this->wn as $sub=>$key) {
2002 if (is_null($key)) {
2003 $joins['wn' . $sub] = array('left', 'watch_nonins', '$ME.ni_id = $UID');
2004 } else {
2005 $joins['wn' . $sub] = array('left', 'watch_nonins', XDB::format('$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5)));
2006 }
2007 }
2008 foreach ($this->wp as $sub=>$key) {
2009 if (is_null($key)) {
2010 $joins['wp' . $sub] = array('left', 'watch_promo');
2011 } else {
2012 $joins['wp' . $sub] = array('left', 'watch_promo', XDB::format('$ME.uid = {?}', substr($key, 5)));
2013 }
2014 }
2015 return $joins;
2016 }
a087cc8d 2017}
8363588b 2018// }}}
3f42a6ad 2019
a087cc8d
FB
2020// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
2021?>