Update following core@7da01959a.
[platal.git] / classes / userfilter.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2010 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
22
23 /******************
24 * CONDITIONS
25 ******************/
26
27 // {{{ abstract class UserFilterCondition
28 /** This class 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 */
37 abstract class UserFilterCondition implements PlFilterCondition
38 {
39 public function export()
40 {
41 throw new Exception("This class is not exportable");
42 }
43 }
44 // }}}
45
46 // {{{ class UFC_HasProfile
47 /** Filters users who have a profile
48 */
49 class UFC_HasProfile extends UserFilterCondition
50 {
51 public function buildCondition(PlFilter $uf)
52 {
53 $uf->requireProfiles();
54 return '$PID IS NOT NULL';
55 }
56 }
57 // }}}
58
59 // {{{ class UFC_AccountType
60 /** Filters users who have one of the given account types
61 */
62 class UFC_AccountType extends UserFilterCondition
63 {
64 private $types;
65
66 public function __construct()
67 {
68 $this->types = pl_flatten(func_get_args());
69 }
70
71 public function buildCondition(PlFilter $uf)
72 {
73 $uf->requireAccounts();
74 return XDB::format('a.type IN {?}', $this->types);
75 }
76 }
77 // }}}
78
79 // {{{ class UFC_AccountPerm
80 /** Filters users who have one of the given permissions
81 */
82 class UFC_AccountPerm extends UserFilterCondition
83 {
84 private $perms;
85
86 public function __construct()
87 {
88 $this->perms = pl_flatten(func_get_args());
89 }
90
91 public function buildCondition(PlFilter $uf)
92 {
93 $uf->requirePerms();
94 $conds = array();
95 foreach ($this->perms as $perm) {
96 $conds[] = XDB::format('FIND_IN_SET({?}, IF(a.user_perms IS NULL, at.perms,
97 CONCAT(at.perms, \',\', a.user_perms)))',
98 $perm);
99 }
100 if (empty($conds)) {
101 return self::COND_TRUE;
102 } else {
103 return implode(' OR ', $conds);
104 }
105 }
106 }
107 // }}}
108
109 // {{{ class UFC_Hruid
110 /** Filters users based on their hruid
111 * @param $val Either an hruid, or a list of those
112 */
113 class UFC_Hruid extends UserFilterCondition
114 {
115 private $hruids;
116
117 public function __construct()
118 {
119 $this->hruids = pl_flatten(func_get_args());
120 }
121
122 public function buildCondition(PlFilter $uf)
123 {
124 $uf->requireAccounts();
125 return XDB::format('a.hruid IN {?}', $this->hruids);
126 }
127 }
128 // }}}
129
130 // {{{ class UFC_Hrpid
131 /** Filters users based on the hrpid of their profiles
132 * @param $val Either an hrpid, or a list of those
133 */
134 class UFC_Hrpid extends UserFilterCondition
135 {
136 private $hrpids;
137
138 public function __construct()
139 {
140 $this->hrpids = pl_flatten(func_get_args());
141 }
142
143 public function buildCondition(PlFilter $uf)
144 {
145 $uf->requireProfiles();
146 return XDB::format('p.hrpid IN {?}', $this->hrpids);
147 }
148 }
149 // }}}
150
151 // {{{ class UFC_Ip
152 /** Filters users based on one of their last IPs
153 * @param $ip IP from which connection are checked
154 */
155 class UFC_Ip extends UserFilterCondition
156 {
157 private $ip;
158
159 public function __construct($ip)
160 {
161 $this->ip = $ip;
162 }
163
164 public function buildCondition(PlFilter $uf)
165 {
166 $sub = $uf->addLoggerFilter();
167 $ip = ip_to_uint($this->ip);
168 return XDB::format($sub . '.ip = {?} OR ' . $sub . '.forward_ip = {?}', $ip, $ip);
169 }
170 }
171 // }}}
172
173 // {{{ class UFC_Comment
174 class UFC_Comment extends UserFilterCondition
175 {
176 private $text;
177
178 public function __construct($text)
179 {
180 $this->text = $text;
181 }
182
183 public function buildCondition(PlFilter $uf)
184 {
185 $uf->requireProfiles();
186 return $uf->getVisibilityCondition('p.freetext_pub') . ' AND p.freetext ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->text);
187 }
188 }
189 // }}}
190
191 // {{{ class UFC_Promo
192 /** Filters users based on promotion
193 * @param $comparison Comparison operator (>, =, ...)
194 * @param $grade Formation on which to restrict, UserFilter::DISPLAY for "any formation"
195 * @param $promo Promotion on which the filter is based
196 */
197 class UFC_Promo extends UserFilterCondition
198 {
199
200 private $grade;
201 private $promo;
202 private $comparison;
203
204 public function __construct($comparison, $grade, $promo)
205 {
206 $this->grade = $grade;
207 $this->comparison = $comparison;
208 $this->promo = $promo;
209 if ($this->grade != UserFilter::DISPLAY) {
210 UserFilter::assertGrade($this->grade);
211 }
212 if ($this->grade == UserFilter::DISPLAY && $this->comparison != '=') {
213 // XXX: we might try to guess the grade from the first char of the promo and forbid only '<= 2004', but allow '<= X2004'
214 Platal::page()->killError("Il n'est pas possible d'appliquer la comparaison '" . $this->comparison . "' aux promotions sans spécifier de formation (X/M/D)");
215 }
216 }
217
218 public function buildCondition(PlFilter $uf)
219 {
220 if ($this->grade == UserFilter::DISPLAY) {
221 $sub = $uf->addDisplayFilter();
222 return XDB::format('pd' . $sub . '.promo ' . $this->comparison . ' {?}', $this->promo);
223 } else {
224 $sub = $uf->addEducationFilter(true, $this->grade);
225 $field = 'pe' . $sub . '.' . UserFilter::promoYear($this->grade);
226 return $field . ' IS NOT NULL AND ' . $field . ' ' . $this->comparison . ' ' . XDB::format('{?}', $this->promo);
227 }
228 }
229 }
230 // }}}
231
232 // {{{ class UFC_SchoolId
233 /** Filters users based on their shoold identifier
234 * @param type Parameter type (Xorg, AX, School)
235 * @param value Array of school ids
236 */
237 class UFC_SchoolId extends UserFilterCondition
238 {
239 const AX = 'ax';
240 const Xorg = 'xorg';
241 const School = 'school';
242
243 private $type;
244 private $id;
245
246 static public function assertType($type)
247 {
248 if ($type != self::AX && $type != self::Xorg && $type != self::School) {
249 Platal::page()->killError("Type de matricule invalide: $type");
250 }
251 }
252
253 /** Construct a UFC_SchoolId
254 * The first argument is the type, all following arguments can be either ids
255 * or arrays of ids to use:
256 * $ufc = new UFC_SchoolId(UFC_SchoolId::AX, $id1, $id2, array($id3, $id4));
257 */
258 public function __construct($type)
259 {
260 $this->type = $type;
261 $ids = func_get_args();
262 array_shift($ids);
263 $this->ids = pl_flatten($ids);
264 self::assertType($type);
265 }
266
267 public function buildCondition(PlFilter $uf)
268 {
269 $uf->requireProfiles();
270 $ids = $this->ids;
271 $type = $this->type;
272 if ($type == self::School) {
273 $type = self::Xorg;
274 $ids = array_map(array('Profile', 'getXorgId'), $ids);
275 }
276 return XDB::format('p.' . $type . '_id IN {?}', $ids);
277 }
278 }
279 // }}}
280
281 // {{{ class UFC_EducationSchool
282 /** Filters users by formation
283 * @param $val The formation to search (either ID or array of IDs)
284 */
285 class UFC_EducationSchool extends UserFilterCondition
286 {
287 private $val;
288
289 public function __construct()
290 {
291 $this->val = pl_flatten(func_get_args());
292 }
293
294 public function buildCondition(PlFilter $uf)
295 {
296 $sub = $uf->addEducationFilter();
297 return XDB::format('pe' . $sub . '.eduid IN {?}', $this->val);
298 }
299 }
300 // }}}
301
302 // {{{ class UFC_EducationDegree
303 class UFC_EducationDegree extends UserFilterCondition
304 {
305 private $diploma;
306
307 public function __construct()
308 {
309 $this->diploma = pl_flatten(func_get_args());
310 }
311
312 public function buildCondition(PlFilter $uf)
313 {
314 $sub = $uf->addEducationFilter();
315 return XDB::format('pe' . $sub . '.degreeid IN {?}', $this->diploma);
316 }
317 }
318 // }}}
319
320 // {{{ class UFC_EducationField
321 class UFC_EducationField extends UserFilterCondition
322 {
323 private $val;
324
325 public function __construct()
326 {
327 $this->val = pl_flatten(func_get_args());
328 }
329
330 public function buildCondition(PlFilter $uf)
331 {
332 $sub = $uf->addEducationFilter();
333 return XDB::format('pe' . $sub . '.fieldid IN {?}', $this->val);
334 }
335 }
336 // }}}
337
338 // {{{ class UFC_Name
339 /** Filters users based on name
340 * @param $type Type of name field on which filtering is done (firstname, lastname...)
341 * @param $text Text on which to filter
342 * @param $mode Flag indicating search type (prefix, suffix, with particule...)
343 */
344 class UFC_Name extends UserFilterCondition
345 {
346 const EXACT = XDB::WILDCARD_EXACT; // 0x000
347 const PREFIX = XDB::WILDCARD_PREFIX; // 0x001
348 const SUFFIX = XDB::WILDCARD_SUFFIX; // 0x002
349 const CONTAINS = XDB::WILDCARD_CONTAINS; // 0x003
350 const PARTICLE = 0x004;
351 const VARIANTS = 0x008;
352
353 private $type;
354 private $text;
355 private $mode;
356
357 public function __construct($type, $text, $mode)
358 {
359 $this->type = $type;
360 $this->text = $text;
361 $this->mode = $mode;
362 }
363
364 private function buildNameQuery($type, $variant, $where, UserFilter $uf)
365 {
366 $sub = $uf->addNameFilter($type, $variant);
367 return str_replace('$ME', 'pn' . $sub, $where);
368 }
369
370 public function buildCondition(PlFilter $uf)
371 {
372 $left = '$ME.name';
373 if (($this->mode & self::PARTICLE) == self::PARTICLE) {
374 $left = 'CONCAT($ME.particle, \' \', $ME.name)';
375 }
376 $right = XDB::formatWildcards($this->mode & self::CONTAINS, $this->text);
377
378 $cond = $left . $right;
379 $conds = array($this->buildNameQuery($this->type, null, $cond, $uf));
380 if (($this->mode & self::VARIANTS) != 0 && isset(Profile::$name_variants[$this->type])) {
381 foreach (Profile::$name_variants[$this->type] as $var) {
382 $conds[] = $this->buildNameQuery($this->type, $var, $cond, $uf);
383 }
384 }
385 return implode(' OR ', $conds);
386 }
387 }
388 // }}}
389
390 // {{{ class UFC_NameTokens
391 /** Selects users based on tokens in their name (for quicksearch)
392 * @param $tokens An array of tokens to search
393 * @param $flags Flags the tokens must have (e.g 'public' for public search)
394 * @param $soundex (bool) Whether those tokens are fulltext or soundex
395 */
396 class UFC_NameTokens extends UserFilterCondition
397 {
398 /* Flags */
399 const FLAG_PUBLIC = 'public';
400
401 private $tokens;
402 private $flags;
403 private $soundex;
404 private $exact;
405
406 public function __construct($tokens, $flags = array(), $soundex = false, $exact = false)
407 {
408 if (is_array($tokens)) {
409 $this->tokens = $tokens;
410 } else {
411 $this->tokens = array($tokens);
412 }
413 if (is_array($flags)) {
414 $this->flags = $flags;
415 } else {
416 $this->flags = array($flags);
417 }
418 $this->soundex = $soundex;
419 $this->exact = $exact;
420 }
421
422 public function buildCondition(PlFilter $uf)
423 {
424 $conds = array();
425 foreach ($this->tokens as $i => $token) {
426 $sub = $uf->addNameTokensFilter($token);
427 if ($this->soundex) {
428 $c = XDB::format($sub . '.soundex = {?}', soundex_fr($token));
429 } else if ($this->exact) {
430 $c = XDB::format($sub . '.token = {?}', $token);
431 } else {
432 $c = $sub . '.token ' . XDB::formatWildcards(XDB::WILDCARD_PREFIX, $token);
433 }
434 if ($this->flags != null) {
435 $c .= XDB::format(' AND ' . $sub . '.flags IN {?}', $this->flags);
436 }
437 $conds[] = $c;
438 }
439
440 return implode(' AND ', $conds);
441 }
442 }
443 // }}}
444
445 // {{{ class UFC_Nationality
446 class UFC_Nationality extends UserFilterCondition
447 {
448 private $val;
449
450 public function __construct()
451 {
452 $this->val = pl_flatten(func_get_args());
453 }
454
455 public function buildCondition(PlFilter $uf)
456 {
457 $uf->requireProfiles();
458 $nat = XDB::formatArray($this->val);
459 $conds = array(
460 'p.nationality1 IN ' . $nat,
461 'p.nationality2 IN ' . $nat,
462 'p.nationality3 IN ' . $nat,
463 );
464 return implode(' OR ', $conds);
465 }
466 }
467 // }}}
468
469 // {{{ class UFC_Dead
470 /** Filters users based on death date
471 * @param $comparison Comparison operator
472 * @param $date Date to which death date should be compared (DateTime object, string or timestamp)
473 */
474 class UFC_Dead extends UserFilterCondition
475 {
476 private $comparison;
477 private $date;
478
479 public function __construct($comparison = null, $date = null)
480 {
481 $this->comparison = $comparison;
482 $this->date = make_datetime($date);
483 }
484
485 public function buildCondition(PlFilter $uf)
486 {
487 $uf->requireProfiles();
488 $str = 'p.deathdate IS NOT NULL';
489 if (!is_null($this->comparison)) {
490 $str .= ' AND p.deathdate ' . $this->comparison . ' ' . XDB::format('{?}', $this->date->format('Y-m-d'));
491 }
492 return $str;
493 }
494 }
495 // }}}
496
497 // {{{ class UFC_Registered
498 /** Filters users based on registration state
499 * @param $active Whether we want to use only "active" users (i.e with a valid redirection)
500 * @param $comparison Comparison operator
501 * @param $date Date to which users registration date should be compared
502 */
503 class UFC_Registered extends UserFilterCondition
504 {
505 private $active;
506 private $comparison;
507 private $date;
508
509 public function __construct($active = false, $comparison = null, $date = null)
510 {
511 $this->active = $active;
512 $this->comparison = $comparison;
513 $this->date = make_datetime($date);
514 }
515
516 public function buildCondition(PlFilter $uf)
517 {
518 $uf->requireAccounts();
519 if ($this->active) {
520 $date = '$UID IS NOT NULL AND a.state = \'active\'';
521 } else {
522 $date = '$UID IS NOT NULL AND a.state != \'pending\'';
523 }
524 if (!is_null($this->comparison)) {
525 $date .= ' AND a.registration_date != \'0000-00-00 00:00:00\' AND a.registration_date ' . $this->comparison . ' ' . XDB::format('{?}', $this->date->format('Y-m-d'));
526 }
527 return $date;
528 }
529 }
530 // }}}
531
532 // {{{ class UFC_ProfileUpdated
533 /** Filters users based on profile update date
534 * @param $comparison Comparison operator
535 * @param $date Date to which profile update date must be compared
536 */
537 class UFC_ProfileUpdated extends UserFilterCondition
538 {
539 private $comparison;
540 private $date;
541
542 public function __construct($comparison = null, $date = null)
543 {
544 $this->comparison = $comparison;
545 $this->date = $date;
546 }
547
548 public function buildCondition(PlFilter $uf)
549 {
550 $uf->requireProfiles();
551 return 'p.last_change ' . $this->comparison . XDB::format(' {?}', date('Y-m-d H:i:s', $this->date));
552 }
553 }
554 // }}}
555
556 // {{{ class UFC_Birthday
557 /** Filters users based on next birthday date
558 * @param $comparison Comparison operator
559 * @param $date Date to which users next birthday date should be compared
560 */
561 class UFC_Birthday extends UserFilterCondition
562 {
563 private $comparison;
564 private $date;
565
566 public function __construct($comparison = null, $date = null)
567 {
568 $this->comparison = $comparison;
569 $this->date = $date;
570 }
571
572 public function buildCondition(PlFilter $uf)
573 {
574 $uf->requireProfiles();
575 return 'p.next_birthday ' . $this->comparison . XDB::format(' {?}', date('Y-m-d', $this->date));
576 }
577 }
578 // }}}
579
580 // {{{ class UFC_Sex
581 /** Filters users based on sex
582 * @parm $sex One of User::GENDER_MALE or User::GENDER_FEMALE, for selecting users
583 */
584 class UFC_Sex extends UserFilterCondition
585 {
586 private $sex;
587 public function __construct($sex)
588 {
589 $this->sex = $sex;
590 }
591
592 public function buildCondition(PlFilter $uf)
593 {
594 if ($this->sex != User::GENDER_MALE && $this->sex != User::GENDER_FEMALE) {
595 return self::COND_FALSE;
596 } else {
597 $uf->requireProfiles();
598 return XDB::format('p.sex = {?}', $this->sex == User::GENDER_FEMALE ? 'female' : 'male');
599 }
600 }
601 }
602 // }}}
603
604 // {{{ class UFC_Group
605 /** Filters users based on group membership
606 * @param $group Group whose members we are selecting
607 * @param $anim Whether to restrict selection to animators of that group
608 */
609 class UFC_Group extends UserFilterCondition
610 {
611 private $group;
612 private $anim;
613 public function __construct($group, $anim = false)
614 {
615 $this->group = $group;
616 $this->anim = $anim;
617 }
618
619 public function buildCondition(PlFilter $uf)
620 {
621 // Groups have AX visibility.
622 if ($uf->getVisibilityLevel() == ProfileVisibility::VIS_PUBLIC) {
623 return self::COND_TRUE;
624 }
625 $sub = $uf->addGroupFilter($this->group);
626 $where = 'gpm' . $sub . '.perms IS NOT NULL';
627 if ($this->anim) {
628 $where .= ' AND gpm' . $sub . '.perms = \'admin\'';
629 }
630 return $where;
631 }
632 }
633 // }}}
634
635 // {{{ class UFC_Binet
636 /** Selects users based on their belonging to a given (list of) binet
637 * @param $binet either a binet_id or an array of binet_ids
638 */
639 class UFC_Binet extends UserFilterCondition
640 {
641 private $val;
642
643 public function __construct()
644 {
645 $this->val = pl_flatten(func_get_args());
646 }
647
648 public function buildCondition(PlFilter $uf)
649 {
650 // Binets are private.
651 if ($uf->getVisibilityLevel() != ProfileVisibility::VIS_PRIVATE) {
652 return self::CONF_TRUE;
653 }
654 $sub = $uf->addBinetsFilter();
655 return XDB::format($sub . '.binet_id IN {?}', $this->val);
656 }
657 }
658 // }}}
659
660 // {{{ class UFC_Section
661 /** Selects users based on section
662 * @param $section ID of the section
663 */
664 class UFC_Section extends UserFilterCondition
665 {
666 private $section;
667
668 public function __construct()
669 {
670 $this->section = pl_flatten(func_get_args());
671 }
672
673 public function buildCondition(PlFilter $uf)
674 {
675 // Sections are private.
676 if ($uf->getVisibilityLevel() != ProfileVisibility::VIS_PRIVATE) {
677 return self::CONF_TRUE;
678 }
679 $uf->requireProfiles();
680 return XDB::format('p.section IN {?}', $this->section);
681 }
682 }
683 // }}}
684
685 // {{{ class UFC_Email
686 /** Filters users based on an email or a list of emails
687 * @param $emails List of emails whose owner must be selected
688 */
689 class UFC_Email extends UserFilterCondition
690 {
691 private $emails;
692 public function __construct()
693 {
694 $this->emails = pl_flatten(func_get_args());
695 }
696
697 public function buildCondition(PlFilter $uf)
698 {
699 $foreign = array();
700 $virtual = array();
701 $aliases = array();
702 $cond = array();
703
704 if (count($this->emails) == 0) {
705 return PlFilterCondition::COND_TRUE;
706 }
707
708 foreach ($this->emails as $entry) {
709 if (User::isForeignEmailAddress($entry)) {
710 $foreign[] = $entry;
711 } else if (User::isVirtualEmailAddress($entry)) {
712 $virtual[] = $entry;
713 } else {
714 @list($user, $domain) = explode('@', $entry);
715 $aliases[] = $user;
716 }
717 }
718
719 if (count($foreign) > 0) {
720 $sub = $uf->addEmailRedirectFilter($foreign);
721 $cond[] = XDB::format('e' . $sub . '.email IS NOT NULL OR a.email IN {?}', $foreign);
722 }
723 if (count($virtual) > 0) {
724 $sub = $uf->addVirtualEmailFilter($virtual);
725 $cond[] = 'vr' . $sub . '.redirect IS NOT NULL';
726 }
727 if (count($aliases) > 0) {
728 $sub = $uf->addAliasFilter($aliases);
729 $cond[] = 'al' . $sub . '.alias IS NOT NULL';
730 }
731 return '(' . implode(') OR (', $cond) . ')';
732 }
733 }
734 // }}}
735
736 // {{{ class UFC_Address
737 abstract class UFC_Address extends UserFilterCondition
738 {
739 /** Valid address type ('hq' is reserved for company addresses)
740 */
741 const TYPE_HOME = 1;
742 const TYPE_PRO = 2;
743 const TYPE_ANY = 3;
744
745 /** Text for these types
746 */
747 protected static $typetexts = array(
748 self::TYPE_HOME => 'home',
749 self::TYPE_PRO => 'pro',
750 );
751
752 protected $type;
753
754 /** Flags for addresses
755 */
756 const FLAG_CURRENT = 0x0001;
757 const FLAG_TEMP = 0x0002;
758 const FLAG_SECOND = 0x0004;
759 const FLAG_MAIL = 0x0008;
760 const FLAG_CEDEX = 0x0010;
761
762 // Binary OR of those flags
763 const FLAG_ANY = 0x001F;
764
765 /** Text of these flags
766 */
767 protected static $flagtexts = array(
768 self::FLAG_CURRENT => 'current',
769 self::FLAG_TEMP => 'temporary',
770 self::FLAG_SECOND => 'secondary',
771 self::FLAG_MAIL => 'mail',
772 self::FLAG_CEDEX => 'cedex',
773 );
774
775 protected $flags;
776
777 public function __construct($type = null, $flags = null)
778 {
779 $this->type = $type;
780 $this->flags = $flags;
781 }
782
783 protected function initConds($sub, $vis_cond)
784 {
785 $conds = array($vis_cond);
786
787 $types = array();
788 foreach (self::$typetexts as $flag => $type) {
789 if ($flag & $this->type) {
790 $types[] = $type;
791 }
792 }
793 if (count($types)) {
794 $conds[] = XDB::format($sub . '.type IN {?}', $types);
795 }
796
797 if ($this->flags != self::FLAG_ANY) {
798 foreach(self::$flagtexts as $flag => $text) {
799 if ($flag & $this->flags) {
800 $conds[] = 'FIND_IN_SET(' . XDB::format('{?}', $text) . ', ' . $sub . '.flags)';
801 }
802 }
803 }
804 return $conds;
805 }
806
807 }
808 // }}}
809
810 // {{{ class UFC_AddressText
811 /** Select users based on their address, using full text search
812 * @param $text Text for filter in fulltext search
813 * @param $textSearchMode Mode for search (one of XDB::WILDCARD_*)
814 * @param $type Filter on address type
815 * @param $flags Filter on address flags
816 * @param $country Filter on address country
817 * @param $locality Filter on address locality
818 */
819 class UFC_AddressText extends UFC_Address
820 {
821
822 private $text;
823 private $textSearchMode;
824
825 public function __construct($text = null, $textSearchMode = XDB::WILDCARD_CONTAINS,
826 $type = null, $flags = self::FLAG_ANY, $country = null, $locality = null)
827 {
828 parent::__construct($type, $flags);
829 $this->text = $text;
830 $this->textSearchMode = $textSearchMode;
831 $this->country = $country;
832 $this->locality = $locality;
833 }
834
835 private function mkMatch($txt)
836 {
837 return XDB::formatWildcards($this->textSearchMode, $txt);
838 }
839
840 public function buildCondition(PlFilter $uf)
841 {
842 $sub = $uf->addAddressFilter();
843 $conds = $this->initConds($sub, $uf->getVisibilityCondition($sub . '.pub'));
844 if ($this->text != null) {
845 $conds[] = $sub . '.text' . $this->mkMatch($this->text);
846 }
847
848 if ($this->country != null) {
849 $subc = $uf->addAddressCountryFilter();
850 $subconds = array();
851 $subconds[] = $subc . '.country' . $this->mkMatch($this->country);
852 $subconds[] = $subc . '.countryFR' . $this->mkMatch($this->country);
853 $conds[] = implode(' OR ', $subconds);
854 }
855
856 if ($this->locality != null) {
857 $subl = $uf->addAddressLocalityFilter();
858 $conds[] = $subl . '.name' . $this->mkMatch($this->locality);
859 }
860
861 return implode(' AND ', $conds);
862 }
863 }
864 // }}}
865
866 // {{{ class UFC_AddressField
867 /** Filters users based on their address,
868 * @param $val Either a code for one of the fields, or an array of such codes
869 * @param $fieldtype The type of field to look for
870 * @param $type Filter on address type
871 * @param $flags Filter on address flags
872 */
873 class UFC_AddressField extends UFC_Address
874 {
875 const FIELD_COUNTRY = 1;
876 const FIELD_ADMAREA = 2;
877 const FIELD_SUBADMAREA = 3;
878 const FIELD_LOCALITY = 4;
879 const FIELD_ZIPCODE = 5;
880
881 /** Data of the filter
882 */
883 private $val;
884 private $fieldtype;
885
886 public function __construct($val, $fieldtype, $type = null, $flags = self::FLAG_ANY)
887 {
888 parent::__construct($type, $flags);
889
890 if (!is_array($val)) {
891 $val = array($val);
892 }
893 $this->val = $val;
894 $this->fieldtype = $fieldtype;
895 }
896
897 public function buildCondition(PlFilter $uf)
898 {
899 $sub = $uf->addAddressFilter();
900 $conds = $this->initConds($sub, $uf->getVisibilityCondition($sub . '.pub'));
901
902 switch ($this->fieldtype) {
903 case self::FIELD_COUNTRY:
904 $field = 'countryId';
905 break;
906 case self::FIELD_ADMAREA:
907 $field = 'administrativeAreaId';
908 break;
909 case self::FIELD_SUBADMAREA:
910 $field = 'subAdministrativeAreaId';
911 break;
912 case self::FIELD_LOCALITY:
913 $field = 'localityId';
914 break;
915 case self::FIELD_ZIPCODE:
916 $field = 'postalCode';
917 break;
918 default:
919 Platal::page()->killError('Invalid address field type: ' . $this->fieldtype);
920 }
921 $conds[] = XDB::format($sub . '.' . $field . ' IN {?}', $this->val);
922
923 return implode(' AND ', $conds);
924 }
925 }
926 // }}}
927
928 // {{{ class UFC_Corps
929 /** Filters users based on the corps they belong to
930 * @param $corps Corps we are looking for (abbreviation)
931 * @param $type Whether we search for original or current corps
932 */
933 class UFC_Corps extends UserFilterCondition
934 {
935 const CURRENT = 1;
936 const ORIGIN = 2;
937
938 private $corps;
939 private $type;
940
941 public function __construct($corps, $type = self::CURRENT)
942 {
943 $this->corps = $corps;
944 $this->type = $type;
945 }
946
947 public function buildCondition(PlFilter $uf)
948 {
949 /** Tables shortcuts:
950 * pc for profile_corps,
951 * pceo for profile_corps_enum - orginal
952 * pcec for profile_corps_enum - current
953 */
954 $sub = $uf->addCorpsFilter($this->type);
955 $cond = $sub . '.abbreviation = ' . $corps;
956 $cond .= ' AND ' . $uf->getVisibilityCondition($sub . '.corps_pub');
957 return $cond;
958 }
959 }
960 // }}}
961
962 // {{{ class UFC_Corps_Rank
963 /** Filters users based on their rank in the corps
964 * @param $rank Rank we are looking for (abbreviation)
965 */
966 class UFC_Corps_Rank extends UserFilterCondition
967 {
968 private $rank;
969 public function __construct($rank)
970 {
971 $this->rank = $rank;
972 }
973
974 public function buildCondition(PlFilter $uf)
975 {
976 /** Tables shortcuts:
977 * pc for profile_corps
978 * pcr for profile_corps_rank
979 */
980 $sub = $uf->addCorpsRankFilter();
981 $cond = $sub . '.abbreviation = ' . $rank;
982 // XXX(x2006barrois): find a way to get rid of that hardcoded
983 // reference to 'pc'.
984 $cond .= ' AND ' . $uf->getVisibilityCondition('pc.corps_pub');
985 return $cond;
986 }
987 }
988 // }}}
989
990 // {{{ class UFC_Job_Company
991 /** Filters users based on the company they belong to
992 * @param $type The field being searched (self::JOBID, self::JOBNAME or self::JOBACRONYM)
993 * @param $value The searched value
994 */
995 class UFC_Job_Company extends UserFilterCondition
996 {
997 const JOBID = 'id';
998 const JOBNAME = 'name';
999 const JOBACRONYM = 'acronym';
1000
1001 private $type;
1002 private $value;
1003
1004 public function __construct($type, $value)
1005 {
1006 $this->assertType($type);
1007 $this->type = $type;
1008 $this->value = $value;
1009 }
1010
1011 private function assertType($type)
1012 {
1013 if ($type != self::JOBID && $type != self::JOBNAME && $type != self::JOBACRONYM) {
1014 Platal::page()->killError("Type de recherche non valide.");
1015 }
1016 }
1017
1018 public function buildCondition(PlFilter $uf)
1019 {
1020 $sub = $uf->addJobCompanyFilter();
1021 $cond = $sub . '.' . $this->type . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->value);
1022 $jsub = $uf->addJobFilter();
1023 $cond .= ' AND ' . $uf->getVisibilityCondition($jsub . '.pub');
1024 return $cond;
1025 }
1026 }
1027 // }}}
1028
1029 // {{{ class UFC_Job_Terms
1030 /** Filters users based on the job terms they assigned to one of their
1031 * jobs.
1032 * @param $val The ID of the job term, or an array of such IDs
1033 */
1034 class UFC_Job_Terms extends UserFilterCondition
1035 {
1036 private $val;
1037
1038 public function __construct($val)
1039 {
1040 if (!is_array($val)) {
1041 $val = array($val);
1042 }
1043 $this->val = $val;
1044 }
1045
1046 public function buildCondition(PlFilter $uf)
1047 {
1048 $sub = $uf->addJobTermsFilter(count($this->val));
1049 $conditions = array();
1050 foreach ($this->val as $i => $jtid) {
1051 $conditions[] = $sub[$i] . '.jtid_1 = ' . XDB::escape($jtid);
1052 }
1053 $jsub = $uf->addJobFilter();
1054 $conditions[] = $uf->getVisibilityCondition($jsub . '.pub');
1055 return implode(' AND ', $conditions);
1056 }
1057 }
1058 // }}}
1059
1060 // {{{ class UFC_Job_Description
1061 /** Filters users based on their job description
1062 * @param $description The text being searched for
1063 * @param $fields The fields to search for (CV, user-defined)
1064 */
1065 class UFC_Job_Description extends UserFilterCondition
1066 {
1067
1068 private $description;
1069 private $fields;
1070
1071 public function __construct($description, $fields)
1072 {
1073 $this->fields = $fields;
1074 $this->description = $description;
1075 }
1076
1077 public function buildCondition(PlFilter $uf)
1078 {
1079 $conds = array();
1080
1081 $jsub = $uf->addJobFilter();
1082 // CV is private => if only CV requested, and not private,
1083 // don't do anything. Otherwise restrict to standard job visibility.
1084 if ($this->fields == UserFilter::JOB_CV) {
1085 if ($uf->getVisibilityLevel() != ProfileVisibility::VIS_PRIVATE) {
1086 return self::CONF_TRUE;
1087 }
1088 } else {
1089 $conds[] = $uf->getVisibilityCondition($jsub . '.pub');
1090 }
1091
1092 if ($this->fields & UserFilter::JOB_USERDEFINED) {
1093 $conds[] = $jsub . '.description ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description);
1094 }
1095 if ($this->fields & UserFilter::JOB_CV && $uf->getVisibilityLevel() == ProfileVisibility::VIS_PRIVATE) {
1096 $uf->requireProfiles();
1097 $conds[] = 'p.cv ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->description);
1098 }
1099 return implode(' OR ', $conds);
1100 }
1101 }
1102 // }}}
1103
1104 // {{{ class UFC_Networking
1105 /** Filters users based on network identity (IRC, ...)
1106 * @param $type Type of network (-1 for any)
1107 * @param $value Value to search
1108 */
1109 class UFC_Networking extends UserFilterCondition
1110 {
1111 private $type;
1112 private $value;
1113
1114 public function __construct($type, $value)
1115 {
1116 $this->type = $type;
1117 $this->value = $value;
1118 }
1119
1120 public function buildCondition(PlFilter $uf)
1121 {
1122 $sub = $uf->addNetworkingFilter();
1123 $conds = array();
1124 $conds[] = $uf->getVisibilityCondition($sub . '.pub');
1125 $conds[] = $sub . '.address ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->value);
1126 if ($this->type != -1) {
1127 $conds[] = $sub . '.nwid = ' . XDB::format('{?}', $this->type);
1128 }
1129 return implode(' AND ', $conds);
1130 }
1131 }
1132 // }}}
1133
1134 // {{{ class UFC_Phone
1135 /** Filters users based on their phone number
1136 * @param $num_type Type of number (pro/user/home)
1137 * @param $phone_type Type of phone (fixed/mobile/fax)
1138 * @param $number Phone number
1139 */
1140 class UFC_Phone extends UserFilterCondition
1141 {
1142 const NUM_PRO = 'pro';
1143 const NUM_USER = 'user';
1144 const NUM_HOME = 'address';
1145 const NUM_ANY = 'any';
1146
1147 const PHONE_FIXED = 'fixed';
1148 const PHONE_MOBILE = 'mobile';
1149 const PHONE_FAX = 'fax';
1150 const PHONE_ANY = 'any';
1151
1152 private $num_type;
1153 private $phone_type;
1154 private $number;
1155
1156 public function __construct($number, $num_type = self::NUM_ANY, $phone_type = self::PHONE_ANY)
1157 {
1158 $phone = new Phone(array('display' => $number));
1159 $phone->format();
1160 $this->number = $phone->search();
1161 $this->num_type = $num_type;
1162 $this->phone_type = $phone_type;
1163 }
1164
1165 public function buildCondition(PlFilter $uf)
1166 {
1167 $sub = $uf->addPhoneFilter();
1168 $conds = array();
1169
1170 $conds[] = $uf->getVisibilityCondition($sub . '.pub');
1171
1172 $conds[] = $sub . '.search_tel = ' . XDB::format('{?}', $this->number);
1173 if ($this->num_type != self::NUM_ANY) {
1174 $conds[] = $sub . '.link_type = ' . XDB::format('{?}', $this->num_type);
1175 }
1176 if ($this->phone_type != self::PHONE_ANY) {
1177 $conds[] = $sub . '.tel_type = ' . XDB::format('{?}', $this->phone_type);
1178 }
1179 return implode(' AND ', $conds);
1180 }
1181 }
1182 // }}}
1183
1184 // {{{ class UFC_Medal
1185 /** Filters users based on their medals
1186 * @param $medal ID of the medal
1187 * @param $grade Grade of the medal (null for 'any')
1188 */
1189 class UFC_Medal extends UserFilterCondition
1190 {
1191 private $medal;
1192 private $grade;
1193
1194 public function __construct($medal, $grade = null)
1195 {
1196 $this->medal = $medal;
1197 $this->grade = $grade;
1198 }
1199
1200 public function buildCondition(PlFilter $uf)
1201 {
1202 $conds = array();
1203
1204 // This will require profiles => table 'p' will be available.
1205 $sub = $uf->addMedalFilter();
1206
1207 $conds[] = $uf->getVisibilityCondition('p.medals_pub');
1208
1209 $conds[] = $sub . '.mid = ' . XDB::format('{?}', $this->medal);
1210 if ($this->grade != null) {
1211 $conds[] = $sub . '.gid = ' . XDB::format('{?}', $this->grade);
1212 }
1213 return implode(' AND ', $conds);
1214 }
1215 }
1216 // }}}
1217
1218 // {{{ class UFC_Photo
1219 /** Filters profiles with photo
1220 */
1221 class UFC_Photo extends UserFilterCondition
1222 {
1223 public function buildCondition(PlFilter $uf)
1224 {
1225 $sub = $uf->addPhotoFilter();
1226 return $sub . '.attach IS NOT NULL AND ' . $uf->getVisibilityCondition($sub . '.pub');
1227 }
1228 }
1229 // }}}
1230
1231 // {{{ class UFC_Mentor
1232 class UFC_Mentor extends UserFilterCondition
1233 {
1234 public function buildCondition(PlFilter $uf)
1235 {
1236 $sub = $uf->addMentorFilter(UserFilter::MENTOR);
1237 return $sub . '.expertise IS NOT NULL';
1238 }
1239 }
1240 // }}}
1241
1242
1243 // {{{ class UFC_Mentor_Expertise
1244 /** Filters users by mentoring expertise
1245 * @param $expertise Domain of expertise
1246 */
1247 class UFC_Mentor_Expertise extends UserFilterCondition
1248 {
1249 private $expertise;
1250
1251 public function __construct($expertise)
1252 {
1253 $this->expertise = $expertise;
1254 }
1255
1256 public function buildCondition(PlFilter $uf)
1257 {
1258 $sub = $uf->addMentorFilter(UserFilter::MENTOR_EXPERTISE);
1259 return $sub . '.expertise ' . XDB::formatWildcards(XDB::WILDCARD_CONTAINS, $this->expertise);
1260 }
1261 }
1262 // }}}
1263
1264 // {{{ class UFC_Mentor_Country
1265 /** Filters users by mentoring country
1266 * @param $country Two-letters code of country being searched
1267 */
1268 class UFC_Mentor_Country extends UserFilterCondition
1269 {
1270 private $country;
1271
1272 public function __construct()
1273 {
1274 $this->country = pl_flatten(func_get_args());
1275 }
1276
1277 public function buildCondition(PlFilter $uf)
1278 {
1279 $sub = $uf->addMentorFilter(UserFilter::MENTOR_COUNTRY);
1280 return $sub . '.country IN ' . XDB::format('{?}', $this->country);
1281 }
1282 }
1283 // }}}
1284
1285 // {{{ class UFC_Mentor_Terms
1286 /** Filters users based on the job terms they used in mentoring.
1287 * @param $val The ID of the job term, or an array of such IDs
1288 */
1289 class UFC_Mentor_Terms extends UserFilterCondition
1290 {
1291 private $val;
1292
1293 public function __construct($val)
1294 {
1295 $this->val = $val;
1296 }
1297
1298 public function buildCondition(PlFilter $uf)
1299 {
1300 $sub = $uf->addMentorFilter(UserFilter::MENTOR_TERM);
1301 return $sub . '.jtid_1 = ' . XDB::escape($this->val);
1302 }
1303 }
1304 // }}}
1305
1306 // {{{ class UFC_UserRelated
1307 /** Filters users based on a relation toward a user
1308 * @param $user User to which searched users are related
1309 */
1310 abstract class UFC_UserRelated extends UserFilterCondition
1311 {
1312 protected $user;
1313 public function __construct(PlUser &$user)
1314 {
1315 $this->user =& $user;
1316 }
1317 }
1318 // }}}
1319
1320 // {{{ class UFC_Contact
1321 /** Filters users who belong to selected user's contacts
1322 */
1323 class UFC_Contact extends UFC_UserRelated
1324 {
1325 public function buildCondition(PlFilter $uf)
1326 {
1327 $sub = $uf->addContactFilter($this->user->id());
1328 return 'c' . $sub . '.contact IS NOT NULL';
1329 }
1330 }
1331 // }}}
1332
1333 // {{{ class UFC_WatchRegistration
1334 /** Filters users being watched by selected user
1335 */
1336 class UFC_WatchRegistration extends UFC_UserRelated
1337 {
1338 public function buildCondition(PlFilter $uf)
1339 {
1340 if (!$this->user->watchType('registration')) {
1341 return PlFilterCondition::COND_FALSE;
1342 }
1343 $uids = $this->user->watchUsers();
1344 if (count($uids) == 0) {
1345 return PlFilterCondition::COND_FALSE;
1346 } else {
1347 return XDB::format('$UID IN {?}', $uids);
1348 }
1349 }
1350 }
1351 // }}}
1352
1353 // {{{ class UFC_WatchPromo
1354 /** Filters users belonging to a promo watched by selected user
1355 * @param $user Selected user (the one watching promo)
1356 * @param $grade Formation the user is watching
1357 */
1358 class UFC_WatchPromo extends UFC_UserRelated
1359 {
1360 private $grade;
1361 public function __construct(PlUser &$user, $grade = UserFilter::GRADE_ING)
1362 {
1363 parent::__construct($user);
1364 $this->grade = $grade;
1365 }
1366
1367 public function buildCondition(PlFilter $uf)
1368 {
1369 $promos = $this->user->watchPromos();
1370 if (count($promos) == 0) {
1371 return PlFilterCondition::COND_FALSE;
1372 } else {
1373 $sube = $uf->addEducationFilter(true, $this->grade);
1374 $field = 'pe' . $sube . '.' . UserFilter::promoYear($this->grade);
1375 return XDB::format($field . ' IN {?}', $promos);
1376 }
1377 }
1378 }
1379 // }}}
1380
1381 // {{{ class UFC_WatchContact
1382 /** Filters users watched by selected user
1383 */
1384 class UFC_WatchContact extends UFC_Contact
1385 {
1386 public function buildCondition(PlFilter $uf)
1387 {
1388 if (!$this->user->watchContacts()) {
1389 return PlFilterCondition::COND_FALSE;
1390 }
1391 return parent::buildCondition($uf);
1392 }
1393 }
1394 // }}}
1395
1396 // {{{ class UFC_MarketingHash
1397 /** Filters users using the hash generated
1398 * to send marketing emails to him.
1399 */
1400 class UFC_MarketingHash extends UserFilterCondition
1401 {
1402 private $hash;
1403
1404 public function __construct($hash)
1405 {
1406 $this->hash = $hash;
1407 }
1408
1409 public function buildCondition(PlFilter $uf)
1410 {
1411 $table = $uf->addMarketingHash();
1412 return XDB::format('rm.hash = {?}', $this->hash);
1413 }
1414 }
1415 // }}}
1416
1417 /******************
1418 * ORDERS
1419 ******************/
1420
1421 // {{{ class UFO_Promo
1422 /** Orders users by promotion
1423 * @param $grade Formation whose promotion users should be sorted by (restricts results to users of that formation)
1424 * @param $desc Whether sort is descending
1425 */
1426 class UFO_Promo extends PlFilterGroupableOrder
1427 {
1428 private $grade;
1429
1430 public function __construct($grade = null, $desc = false)
1431 {
1432 parent::__construct($desc);
1433 $this->grade = $grade;
1434 }
1435
1436 protected function getSortTokens(PlFilter $uf)
1437 {
1438 if (UserFilter::isGrade($this->grade)) {
1439 $sub = $uf->addEducationFilter($this->grade);
1440 return 'pe' . $sub . '.' . UserFilter::promoYear($this->grade);
1441 } else {
1442 $sub = $uf->addDisplayFilter();
1443 return 'pd' . $sub . '.promo';
1444 }
1445 }
1446 }
1447 // }}}
1448
1449 // {{{ class UFO_Name
1450 /** Sorts users by name
1451 * @param $type Type of name on which to sort (firstname...)
1452 * @param $variant Variant of that name to use (marital, ordinary...)
1453 * @param $particle Set to true if particles should be included in the sorting order
1454 * @param $desc If sort order should be descending
1455 */
1456 class UFO_Name extends PlFilterOrder
1457 {
1458 private $type;
1459 private $variant;
1460 private $particle;
1461
1462 public function __construct($type, $variant = null, $particle = false, $desc = false)
1463 {
1464 parent::__construct($desc);
1465 $this->type = $type;
1466 $this->variant = $variant;
1467 $this->particle = $particle;
1468 }
1469
1470 protected function getSortTokens(PlFilter $uf)
1471 {
1472 if (Profile::isDisplayName($this->type)) {
1473 $sub = $uf->addDisplayFilter();
1474 $token = 'pd' . $sub . '.' . $this->type;
1475 if ($uf->accountsRequired()) {
1476 $account_token = Profile::getAccountEquivalentName($this->type);
1477 return 'IFNULL(' . $token . ', a.' . $account_token . ')';
1478 } else {
1479 return $token;
1480 }
1481 } else {
1482 $sub = $uf->addNameFilter($this->type, $this->variant);
1483 if ($this->particle) {
1484 return 'CONCAT(pn' . $sub . '.particle, \' \', pn' . $sub . '.name)';
1485 } else {
1486 return 'pn' . $sub . '.name';
1487 }
1488 }
1489 }
1490 }
1491 // }}}
1492
1493 // {{{ class UFO_Score
1494 class UFO_Score extends PlFilterOrder
1495 {
1496 protected function getSortTokens(PlFilter $uf)
1497 {
1498 $toks = $uf->getNameTokens();
1499 $scores = array();
1500
1501 // If there weren't any sort tokens, we shouldn't sort by score, sort by NULL instead
1502 if (count($toks) == 0) {
1503 return 'NULL';
1504 }
1505
1506 foreach ($toks as $sub => $token) {
1507 $scores[] = XDB::format('SUM(' . $sub . '.score + IF (' . $sub . '.token = {?}, 5, 0) )', $token);
1508 }
1509 return implode(' + ', $scores);
1510 }
1511 }
1512 // }}}
1513
1514 // {{{ class UFO_Registration
1515 /** Sorts users based on registration date
1516 */
1517 class UFO_Registration extends PlFilterOrder
1518 {
1519 protected function getSortTokens(PlFilter $uf)
1520 {
1521 $uf->requireAccounts();
1522 return 'a.registration_date';
1523 }
1524 }
1525 // }}}
1526
1527 // {{{ class UFO_Birthday
1528 /** Sorts users based on next birthday date
1529 */
1530 class UFO_Birthday extends PlFilterOrder
1531 {
1532 protected function getSortTokens(PlFilter $uf)
1533 {
1534 $uf->requireProfiles();
1535 return 'p.next_birthday';
1536 }
1537 }
1538 // }}}
1539
1540 // {{{ class UFO_ProfileUpdate
1541 /** Sorts users based on last profile update
1542 */
1543 class UFO_ProfileUpdate extends PlFilterOrder
1544 {
1545 protected function getSortTokens(PlFilter $uf)
1546 {
1547 $uf->requireProfiles();
1548 return 'p.last_change';
1549 }
1550 }
1551 // }}}
1552
1553 // {{{ class UFO_Death
1554 /** Sorts users based on death date
1555 */
1556 class UFO_Death extends PlFilterOrder
1557 {
1558 protected function getSortTokens(PlFilter $uf)
1559 {
1560 $uf->requireProfiles();
1561 return 'p.deathdate';
1562 }
1563 }
1564 // }}}
1565
1566 // {{{ class UFO_Uid
1567 /** Sorts users based on their uid
1568 */
1569 class UFO_Uid extends PlFilterOrder
1570 {
1571 protected function getSortTokens(PlFilter $uf)
1572 {
1573 $uf->requireAccounts();
1574 return '$UID';
1575 }
1576 }
1577 // }}}
1578
1579 // {{{ class UFO_Hruid
1580 /** Sorts users based on their hruid
1581 */
1582 class UFO_Hruid extends PlFilterOrder
1583 {
1584 protected function getSortTokens(PlFilter $uf)
1585 {
1586 $uf->requireAccounts();
1587 return 'a.hruid';
1588 }
1589 }
1590 // }}}
1591
1592 // {{{ class UFO_Pid
1593 /** Sorts users based on their pid
1594 */
1595 class UFO_Pid extends PlFilterOrder
1596 {
1597 protected function getSortTokens(PlFilter $uf)
1598 {
1599 $uf->requireProfiles();
1600 return '$PID';
1601 }
1602 }
1603 // }}}
1604
1605 // {{{ class UFO_Hrpid
1606 /** Sorts users based on their hrpid
1607 */
1608 class UFO_Hrpid extends PlFilterOrder
1609 {
1610 protected function getSortTokens(PlFilter $uf)
1611 {
1612 $uf->requireProfiles();
1613 return 'p.hrpid';
1614 }
1615 }
1616 // }}}
1617
1618
1619 /***********************************
1620 *********************************
1621 USER FILTER CLASS
1622 *********************************
1623 ***********************************/
1624
1625 // {{{ class UserFilter
1626 /** This class provides a convenient and centralized way of filtering users.
1627 *
1628 * Usage:
1629 * $uf = new UserFilter(new UFC_Blah($x, $y), new UFO_Coin($z, $t));
1630 *
1631 * Resulting UserFilter can be used to:
1632 * - get a list of User objects matching the filter
1633 * - get a list of UIDs matching the filter
1634 * - get the number of users matching the filter
1635 * - check whether a given User matches the filter
1636 * - filter a list of User objects depending on whether they match the filter
1637 *
1638 * Usage for UFC and UFO objects:
1639 * A UserFilter will call all private functions named XXXJoins.
1640 * These functions must return an array containing the list of join
1641 * required by the various UFC and UFO associated to the UserFilter.
1642 * Entries in those returned array are of the following form:
1643 * 'join_tablealias' => array('join_type', 'joined_table', 'join_criter')
1644 * which will be translated into :
1645 * join_type JOIN joined_table AS join_tablealias ON (join_criter)
1646 * in the final query.
1647 *
1648 * In the join_criter text, $ME is replaced with 'join_tablealias', $PID with
1649 * profile.pid, and $UID with accounts.uid.
1650 *
1651 * For each kind of "JOIN" needed, a function named addXXXFilter() should be defined;
1652 * its parameter will be used to set various private vars of the UserFilter describing
1653 * the required joins ; such a function shall return the "join_tablealias" to use
1654 * when referring to the joined table.
1655 *
1656 * For example, if data from profile_job must be available to filter results,
1657 * the UFC object will call $uf-addJobFilter(), which will set the 'with_pj' var and
1658 * return 'pj', the short name to use when referring to profile_job; when building
1659 * the query, calling the jobJoins function will return an array containing a single
1660 * row:
1661 * 'pj' => array('left', 'profile_job', '$ME.pid = $UID');
1662 *
1663 * The 'register_optional' function can be used to generate unique table aliases when
1664 * the same table has to be joined several times with different aliases.
1665 */
1666 class UserFilter extends PlFilter
1667 {
1668 protected $joinMethods = array();
1669
1670 protected $joinMetas = array(
1671 '$PID' => 'p.pid',
1672 '$UID' => 'a.uid',
1673 );
1674
1675 private $root;
1676 private $sort = array();
1677 private $grouper = null;
1678 private $query = null;
1679 private $orderby = null;
1680
1681 // Store the current 'search' visibility.
1682 private $profile_visibility = null;
1683
1684 private $lastusercount = null;
1685 private $lastprofilecount = null;
1686
1687 public function __construct($cond = null, $sort = null)
1688 {
1689 if (empty($this->joinMethods)) {
1690 $class = new ReflectionClass('UserFilter');
1691 foreach ($class->getMethods() as $method) {
1692 $name = $method->getName();
1693 if (substr($name, -5) == 'Joins' && $name != 'buildJoins') {
1694 $this->joinMethods[] = $name;
1695 }
1696 }
1697 }
1698 if (!is_null($cond)) {
1699 if ($cond instanceof PlFilterCondition) {
1700 $this->setCondition($cond);
1701 }
1702 }
1703 if (!is_null($sort)) {
1704 if ($sort instanceof PlFilterOrder) {
1705 $this->addSort($sort);
1706 } else if (is_array($sort)) {
1707 foreach ($sort as $s) {
1708 $this->addSort($s);
1709 }
1710 }
1711 }
1712
1713 // This will set the visibility to the default correct level.
1714 $this->profile_visibility = new ProfileVisibility();
1715 }
1716
1717 public function getVisibilityLevels()
1718 {
1719 return $this->profile_visibility->levels();
1720 }
1721
1722 public function getVisibilityLevel()
1723 {
1724 return $this->profile_visibility->level();
1725 }
1726
1727 public function restrictVisibilityTo($level)
1728 {
1729 $this->profile_visibility->setLevel($level);
1730 }
1731
1732 public function getVisibilityCondition($field)
1733 {
1734 return $field . ' IN ' . XDB::formatArray($this->getVisibilityLevels());
1735 }
1736
1737 private function buildQuery()
1738 {
1739 // The root condition is built first because some orders need info
1740 // available only once all UFC have set their conditions (UFO_Score)
1741 if (is_null($this->query)) {
1742 $where = $this->root->buildCondition($this);
1743 $where = str_replace(array_keys($this->joinMetas),
1744 $this->joinMetas,
1745 $where);
1746 }
1747 if (is_null($this->orderby)) {
1748 $orders = array();
1749 foreach ($this->sort as $sort) {
1750 $orders = array_merge($orders, $sort->buildSort($this));
1751 }
1752 if (count($orders) == 0) {
1753 $this->orderby = '';
1754 } else {
1755 $this->orderby = 'ORDER BY ' . implode(', ', $orders);
1756 }
1757 $this->orderby = str_replace(array_keys($this->joinMetas),
1758 $this->joinMetas,
1759 $this->orderby);
1760 }
1761 if (is_null($this->query)) {
1762 if ($this->with_accounts) {
1763 $from = 'accounts AS a';
1764 } else {
1765 $this->requireProfiles();
1766 $from = 'profiles AS p';
1767 }
1768 $joins = $this->buildJoins();
1769 $this->query = 'FROM ' . $from . '
1770 ' . $joins . '
1771 WHERE (' . $where . ')';
1772 }
1773 }
1774
1775 public function hasGroups()
1776 {
1777 return $this->grouper != null;
1778 }
1779
1780 public function getGroups()
1781 {
1782 return $this->getUIDGroups();
1783 }
1784
1785 public function getUIDGroups()
1786 {
1787 $this->requireAccounts();
1788 $this->buildQuery();
1789 $token = $this->grouper->getGroupToken($this);
1790
1791 $groups = XDB::rawFetchAllRow('SELECT ' . $token . ', COUNT(a.uid)
1792 ' . $this->query . '
1793 GROUP BY ' . $token,
1794 0);
1795 return $groups;
1796 }
1797
1798 public function getPIDGroups()
1799 {
1800 $this->requireProfiles();
1801 $this->buildQuery();
1802 $token = $this->grouper->getGroupToken($this);
1803
1804 $groups = XDB::rawFetchAllRow('SELECT ' . $token . ', COUNT(p.pid)
1805 ' . $this->query . '
1806 GROUP BY ' . $token,
1807 0);
1808 return $groups;
1809 }
1810
1811 private function getUIDList($uids = null, PlLimit &$limit)
1812 {
1813 $this->requireAccounts();
1814 $this->buildQuery();
1815 $lim = $limit->getSql();
1816 $cond = '';
1817 if (!empty($uids)) {
1818 $cond = XDB::format(' AND a.uid IN {?}', $uids);
1819 }
1820 $fetched = XDB::rawFetchColumn('SELECT SQL_CALC_FOUND_ROWS a.uid
1821 ' . $this->query . $cond . '
1822 GROUP BY a.uid
1823 ' . $this->orderby . '
1824 ' . $lim);
1825 $this->lastusercount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()');
1826 return $fetched;
1827 }
1828
1829 private function getPIDList($pids = null, PlLimit &$limit)
1830 {
1831 $this->requireProfiles();
1832 $this->buildQuery();
1833 $lim = $limit->getSql();
1834 $cond = '';
1835 if (!is_null($pids)) {
1836 $cond = XDB::format(' AND p.pid IN {?}', $pids);
1837 }
1838 $fetched = XDB::rawFetchColumn('SELECT SQL_CALC_FOUND_ROWS p.pid
1839 ' . $this->query . $cond . '
1840 GROUP BY p.pid
1841 ' . $this->orderby . '
1842 ' . $lim);
1843 $this->lastprofilecount = (int)XDB::fetchOneCell('SELECT FOUND_ROWS()');
1844 return $fetched;
1845 }
1846
1847 private static function defaultLimit($limit) {
1848 if ($limit == null) {
1849 return new PlLimit();
1850 } else {
1851 return $limit;
1852 }
1853 }
1854
1855 /** Check that the user match the given rule.
1856 */
1857 public function checkUser(PlUser &$user)
1858 {
1859 $this->requireAccounts();
1860 $this->buildQuery();
1861 $count = (int)XDB::rawFetchOneCell('SELECT COUNT(*)
1862 ' . $this->query
1863 . XDB::format(' AND a.uid = {?}', $user->id()));
1864 return $count == 1;
1865 }
1866
1867 /** Check that the profile match the given rule.
1868 */
1869 public function checkProfile(Profile &$profile)
1870 {
1871 $this->requireProfiles();
1872 $this->buildQuery();
1873 $count = (int)XDB::rawFetchOneCell('SELECT COUNT(*)
1874 ' . $this->query
1875 . XDB::format(' AND p.pid = {?}', $profile->id()));
1876 return $count == 1;
1877 }
1878
1879 /** Default filter is on users
1880 */
1881 public function filter(array $users, $limit = null)
1882 {
1883 return $this->filterUsers($users, self::defaultLimit($limit));
1884 }
1885
1886 /** Filter a list of users to extract the users matching the rule.
1887 */
1888 public function filterUsers(array $users, $limit = null)
1889 {
1890 $limit = self::defaultLimit($limit);
1891 $this->requireAccounts();
1892 $this->buildQuery();
1893 $table = array();
1894 $uids = array();
1895 foreach ($users as $user) {
1896 if ($user instanceof PlUser) {
1897 $uid = $user->id();
1898 } else {
1899 $uid = $user;
1900 }
1901 $uids[] = $uid;
1902 $table[$uid] = $user;
1903 }
1904 $fetched = $this->getUIDList($uids, $limit);
1905 $output = array();
1906 foreach ($fetched as $uid) {
1907 $output[] = $table[$uid];
1908 }
1909 return $output;
1910 }
1911
1912 /** Filter a list of profiles to extract the users matching the rule.
1913 */
1914 public function filterProfiles(array $profiles, $limit = null)
1915 {
1916 $limit = self::defaultLimit($limit);
1917 $this->requireProfiles();
1918 $this->buildQuery();
1919 $table = array();
1920 $pids = array();
1921 foreach ($profiles as $profile) {
1922 if ($profile instanceof Profile) {
1923 $pid = $profile->id();
1924 } else {
1925 $pid = $profile;
1926 }
1927 $pids[] = $pid;
1928 $table[$pid] = $profile;
1929 }
1930 $fetched = $this->getPIDList($pids, $limit);
1931 $output = array();
1932 foreach ($fetched as $pid) {
1933 $output[] = $table[$pid];
1934 }
1935 return $output;
1936 }
1937
1938 public function getUIDs($limit = null)
1939 {
1940 $limit = self::defaultLimit($limit);
1941 return $this->getUIDList(null, $limit);
1942 }
1943
1944 public function getUID($pos = 0)
1945 {
1946 $uids =$this->getUIDList(null, new PlLimit(1, $pos));
1947 if (count($uids) == 0) {
1948 return null;
1949 } else {
1950 return $uids[0];
1951 }
1952 }
1953
1954 public function getPIDs($limit = null)
1955 {
1956 $limit = self::defaultLimit($limit);
1957 return $this->getPIDList(null, $limit);
1958 }
1959
1960 public function getPID($pos = 0)
1961 {
1962 $pids =$this->getPIDList(null, new PlLimit(1, $pos));
1963 if (count($pids) == 0) {
1964 return null;
1965 } else {
1966 return $pids[0];
1967 }
1968 }
1969
1970 public function getUsers($limit = null)
1971 {
1972 return User::getBulkUsersWithUIDs($this->getUIDs($limit));
1973 }
1974
1975 public function getUser($pos = 0)
1976 {
1977 $uid = $this->getUID($pos);
1978 if ($uid == null) {
1979 return null;
1980 } else {
1981 return User::getWithUID($uid);
1982 }
1983 }
1984
1985 public function iterUsers($limit = null)
1986 {
1987 return User::iterOverUIDs($this->getUIDs($limit));
1988 }
1989
1990 public function getProfiles($limit = null, $fields = 0x0000, $visibility = null)
1991 {
1992 return Profile::getBulkProfilesWithPIDs($this->getPIDs($limit), $fields, $visibility);
1993 }
1994
1995 public function getProfile($pos = 0, $fields = 0x0000, $visibility = null)
1996 {
1997 $pid = $this->getPID($pos);
1998 if ($pid == null) {
1999 return null;
2000 } else {
2001 return Profile::get($pid, $fields, $visibility);
2002 }
2003 }
2004
2005 public function iterProfiles($limit = null, $fields = 0x0000, $visibility = null)
2006 {
2007 return Profile::iterOverPIDs($this->getPIDs($limit), true, $fields, $visibility);
2008 }
2009
2010 public function get($limit = null)
2011 {
2012 return $this->getUsers($limit);
2013 }
2014
2015 public function getIds($limit = null)
2016 {
2017 return $this->getUIDs();
2018 }
2019
2020 public function getTotalCount()
2021 {
2022 return $this->getTotalUserCount();
2023 }
2024
2025 public function getTotalUserCount()
2026 {
2027 if (is_null($this->lastusercount)) {
2028 $this->requireAccounts();
2029 $this->buildQuery();
2030 return (int)XDB::rawFetchOneCell('SELECT COUNT(DISTINCT a.uid)
2031 ' . $this->query);
2032 } else {
2033 return $this->lastusercount;
2034 }
2035 }
2036
2037 public function getTotalProfileCount()
2038 {
2039 if (is_null($this->lastprofilecount)) {
2040 $this->requireProfiles();
2041 $this->buildQuery();
2042 return (int)XDB::rawFetchOneCell('SELECT COUNT(DISTINCT p.pid)
2043 ' . $this->query);
2044 } else {
2045 return $this->lastprofilecount;
2046 }
2047 }
2048
2049 public function setCondition(PlFilterCondition $cond)
2050 {
2051 $this->root =& $cond;
2052 $this->query = null;
2053 }
2054
2055 public function addSort(PlFilterOrder $sort)
2056 {
2057 if (count($this->sort) == 0 && $sort instanceof PlFilterGroupableOrder)
2058 {
2059 $this->grouper = $sort;
2060 }
2061 $this->sort[] = $sort;
2062 $this->orderby = null;
2063 }
2064
2065 public function export()
2066 {
2067 $export = array('condition' => $this->root->export());
2068 if (!empty($this->sort)) {
2069 $export['sort'] = array();
2070 foreach ($this->sort as $sort) {
2071 $export['sort'][] = $sort->export();
2072 }
2073 }
2074 return $export;
2075 }
2076
2077 static public function getLegacy($promo_min, $promo_max)
2078 {
2079 if ($promo_min != 0) {
2080 $min = new UFC_Promo('>=', self::GRADE_ING, intval($promo_min));
2081 } else {
2082 $min = new PFC_True();
2083 }
2084 if ($promo_max != 0) {
2085 $max = new UFC_Promo('<=', self::GRADE_ING, intval($promo_max));
2086 } else {
2087 $max = new PFC_True();
2088 }
2089 return new UserFilter(new PFC_And($min, $max));
2090 }
2091
2092 static public function sortByName()
2093 {
2094 return array(new UFO_Name(Profile::LASTNAME), new UFO_Name(Profile::FIRSTNAME));
2095 }
2096
2097 static public function sortByPromo()
2098 {
2099 return array(new UFO_Promo(), new UFO_Name(Profile::LASTNAME), new UFO_Name(Profile::FIRSTNAME));
2100 }
2101
2102 static private function getDBSuffix($string)
2103 {
2104 if (is_array($string)) {
2105 if (count($string) == 1) {
2106 return self::getDBSuffix(array_pop($string));
2107 }
2108 return md5(implode('|', $string));
2109 } else {
2110 return preg_replace('/[^a-z0-9]/i', '', $string);
2111 }
2112 }
2113
2114
2115 /** Stores a new (and unique) table alias in the &$table table
2116 * @param &$table Array in which the table alias must be stored
2117 * @param $val Value which will then be used to build the join
2118 * @return Name of the newly created alias
2119 */
2120 private $option = 0;
2121 private function register_optional(array &$table, $val)
2122 {
2123 if (is_null($val)) {
2124 $sub = $this->option++;
2125 $index = null;
2126 } else {
2127 $sub = self::getDBSuffix($val);
2128 $index = $val;
2129 }
2130 $sub = '_' . $sub;
2131 $table[$sub] = $index;
2132 return $sub;
2133 }
2134
2135 /** PROFILE VS ACCOUNT
2136 */
2137 private $with_profiles = false;
2138 private $with_accounts = false;
2139 public function requireAccounts()
2140 {
2141 $this->with_accounts = true;
2142 }
2143
2144 public function accountsRequired()
2145 {
2146 return $this->with_accounts;
2147 }
2148
2149 public function requireProfiles()
2150 {
2151 $this->with_profiles = true;
2152 }
2153
2154 public function profilesRequired()
2155 {
2156 return $this->with_profiles;
2157 }
2158
2159 protected function accountJoins()
2160 {
2161 $joins = array();
2162 if ($this->with_profiles && $this->with_accounts) {
2163 $joins['ap'] = PlSqlJoin::left('account_profiles', '$ME.uid = $UID AND FIND_IN_SET(\'owner\', ap.perms)');
2164 $joins['p'] = PlSqlJoin::left('profiles', '$PID = ap.pid');
2165 }
2166 return $joins;
2167 }
2168
2169 /** PERMISSIONS
2170 */
2171 private $at = false;
2172 public function requirePerms()
2173 {
2174 $this->requireAccounts();
2175 $this->at = true;
2176 return 'at';
2177 }
2178
2179 protected function permJoins()
2180 {
2181 if ($this->at) {
2182 return array('at' => PlSqlJoin::left('account_types', '$ME.type = a.type'));
2183 } else {
2184 return array();
2185 }
2186 }
2187
2188 /** DISPLAY
2189 */
2190 const DISPLAY = 'display';
2191 private $pd = false;
2192 public function addDisplayFilter()
2193 {
2194 $this->requireProfiles();
2195 $this->pd = true;
2196 return '';
2197 }
2198
2199 protected function displayJoins()
2200 {
2201 if ($this->pd) {
2202 return array('pd' => PlSqlJoin::left('profile_display', '$ME.pid = $PID'));
2203 } else {
2204 return array();
2205 }
2206 }
2207
2208 /** LOGGER
2209 */
2210
2211 private $with_logger = false;
2212 public function addLoggerFilter()
2213 {
2214 $this->with_logger = true;
2215 $this->requireAccounts();
2216 return 'ls';
2217 }
2218 protected function loggerJoins()
2219 {
2220 $joins = array();
2221 if ($this->with_logger) {
2222 $joins['ls'] = PlSqlJoin::left('log_sessions', '$ME.uid = $UID');
2223 }
2224 return $joins;
2225 }
2226
2227 /** NAMES
2228 */
2229
2230 static public function assertName($name)
2231 {
2232 if (!DirEnum::getID(DirEnum::NAMETYPES, $name)) {
2233 Platal::page()->kill('Invalid name type: ' . $name);
2234 }
2235 }
2236
2237 private $pn = array();
2238 public function addNameFilter($type, $variant = null)
2239 {
2240 $this->requireProfiles();
2241 if (!is_null($variant)) {
2242 $ft = $type . '_' . $variant;
2243 } else {
2244 $ft = $type;
2245 }
2246 $sub = '_' . $ft;
2247 self::assertName($ft);
2248
2249 if (!is_null($variant) && $variant == 'other') {
2250 $sub .= $this->option++;
2251 }
2252 $this->pn[$sub] = DirEnum::getID(DirEnum::NAMETYPES, $ft);
2253 return $sub;
2254 }
2255
2256 protected function nameJoins()
2257 {
2258 $joins = array();
2259 foreach ($this->pn as $sub => $type) {
2260 $joins['pn' . $sub] = PlSqlJoin::left('profile_name', '$ME.pid = $PID AND $ME.typeid = {?}', $type);
2261 }
2262 return $joins;
2263 }
2264
2265 /** NAMETOKENS
2266 */
2267 private $name_tokens = array();
2268 private $nb_tokens = 0;
2269
2270 public function addNameTokensFilter($token)
2271 {
2272 $this->requireProfiles();
2273 $sub = 'sn' . (1 + $this->nb_tokens);
2274 $this->nb_tokens++;
2275 $this->name_tokens[$sub] = $token;
2276 return $sub;
2277 }
2278
2279 protected function nameTokensJoins()
2280 {
2281 /* We don't return joins, since with_sn forces the SELECT to run on search_name first */
2282 $joins = array();
2283 foreach ($this->name_tokens as $sub => $token) {
2284 $joins[$sub] = PlSqlJoin::left('search_name', '$ME.pid = $PID');
2285 }
2286 return $joins;
2287 }
2288
2289 public function getNameTokens()
2290 {
2291 return $this->name_tokens;
2292 }
2293
2294 /** NATIONALITY
2295 */
2296
2297 private $with_nat = false;
2298 public function addNationalityFilter()
2299 {
2300 $this->with_nat = true;
2301 return 'ngc';
2302 }
2303
2304 protected function nationalityJoins()
2305 {
2306 $joins = array();
2307 if ($this->with_nat) {
2308 $joins['ngc'] = PlSqlJoin::left('geoloc_countries', '$ME.iso_3166_1_a2 = p.nationality1 OR $ME.iso_3166_1_a2 = p.nationality2 OR $ME.iso_3166_1_a2 = p.nationality3');
2309 }
2310 return $joins;
2311 }
2312
2313 /** EDUCATION
2314 */
2315 const GRADE_ING = 'Ing.';
2316 const GRADE_PHD = 'PhD';
2317 const GRADE_MST = 'M%';
2318 static public function isGrade($grade)
2319 {
2320 return ($grade !== 0) && ($grade == self::GRADE_ING || $grade == self::GRADE_PHD || $grade == self::GRADE_MST);
2321 }
2322
2323 static public function assertGrade($grade)
2324 {
2325 if (!self::isGrade($grade)) {
2326 Platal::page()->killError("DiplĂ´me non valide: $grade");
2327 }
2328 }
2329
2330 static public function promoYear($grade)
2331 {
2332 // XXX: Definition of promotion for phds and masters might change in near future.
2333 return ($grade == UserFilter::GRADE_ING) ? 'entry_year' : 'grad_year';
2334 }
2335
2336 private $pepe = array();
2337 private $with_pee = false;
2338 public function addEducationFilter($x = false, $grade = null)
2339 {
2340 $this->requireProfiles();
2341 if (!$x) {
2342 $index = $this->option;
2343 $sub = $this->option++;
2344 } else {
2345 self::assertGrade($grade);
2346 $index = $grade;
2347 $sub = $grade[0];
2348 $this->with_pee = true;
2349 }
2350 $sub = '_' . $sub;
2351 $this->pepe[$index] = $sub;
2352 return $sub;
2353 }
2354
2355 protected function educationJoins()
2356 {
2357 $joins = array();
2358 if ($this->with_pee) {
2359 $joins['pee'] = PlSqlJoin::inner('profile_education_enum', 'pee.abbreviation = \'X\'');
2360 }
2361 foreach ($this->pepe as $grade => $sub) {
2362 if ($this->isGrade($grade)) {
2363 $joins['pe' . $sub] = PlSqlJoin::left('profile_education', '$ME.eduid = pee.id AND $ME.pid = $PID');
2364 $joins['pede' . $sub] = PlSqlJoin::inner('profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid AND $ME.abbreviation LIKE {?}', $grade);
2365 } else {
2366 $joins['pe' . $sub] = PlSqlJoin::left('profile_education', '$ME.pid = $PID');
2367 $joins['pee' . $sub] = PlSqlJoin::inner('profile_education_enum', '$ME.id = pe' . $sub . '.eduid');
2368 $joins['pede' . $sub] = PlSqlJoin::inner('profile_education_degree_enum', '$ME.id = pe' . $sub . '.degreeid');
2369 }
2370 }
2371 return $joins;
2372 }
2373
2374
2375 /** GROUPS
2376 */
2377 private $gpm = array();
2378 public function addGroupFilter($group = null)
2379 {
2380 $this->requireAccounts();
2381 if (!is_null($group)) {
2382 if (is_int($group) || ctype_digit($group)) {
2383 $index = $sub = $group;
2384 } else {
2385 $index = $group;
2386 $sub = self::getDBSuffix($group);
2387 }
2388 } else {
2389 $sub = 'group_' . $this->option++;
2390 $index = null;
2391 }
2392 $sub = '_' . $sub;
2393 $this->gpm[$sub] = $index;
2394 return $sub;
2395 }
2396
2397 protected function groupJoins()
2398 {
2399 $joins = array();
2400 foreach ($this->gpm as $sub => $key) {
2401 if (is_null($key)) {
2402 $joins['gpa' . $sub] = PlSqlJoin::inner('groups');
2403 $joins['gpm' . $sub] = PlSqlJoin::left('group_members', '$ME.uid = $UID AND $ME.asso_id = gpa' . $sub . '.id');
2404 } else if (is_int($key) || ctype_digit($key)) {
2405 $joins['gpm' . $sub] = PlSqlJoin::left('group_members', '$ME.uid = $UID AND $ME.asso_id = ' . $key);
2406 } else {
2407 $joins['gpa' . $sub] = PlSqlJoin::inner('groups', '$ME.diminutif = {?}', $key);
2408 $joins['gpm' . $sub] = PlSqlJoin::left('group_members', '$ME.uid = $UID AND $ME.asso_id = gpa' . $sub . '.id');
2409 }
2410 }
2411 return $joins;
2412 }
2413
2414 /** BINETS
2415 */
2416
2417 private $with_bi = false;
2418 private $with_bd = false;
2419 public function addBinetsFilter($with_enum = false)
2420 {
2421 $this->requireProfiles();
2422 $this->with_bi = true;
2423 if ($with_enum) {
2424 $this->with_bd = true;
2425 return 'bd';
2426 } else {
2427 return 'bi';
2428 }
2429 }
2430
2431 protected function binetsJoins()
2432 {
2433 $joins = array();
2434 if ($this->with_bi) {
2435 $joins['bi'] = PlSqlJoin::left('profile_binets', '$ME.pid = $PID');
2436 }
2437 if ($this->with_bd) {
2438 $joins['bd'] = PlSqlJoin::left('profile_binet_enum', '$ME.id = bi.binet_id');
2439 }
2440 return $joins;
2441 }
2442
2443 /** EMAILS
2444 */
2445 private $e = array();
2446 public function addEmailRedirectFilter($email = null)
2447 {
2448 $this->requireAccounts();
2449 return $this->register_optional($this->e, $email);
2450 }
2451
2452 private $ve = array();
2453 public function addVirtualEmailFilter($email = null)
2454 {
2455 $this->addAliasFilter(self::ALIAS_FORLIFE);
2456 return $this->register_optional($this->ve, $email);
2457 }
2458
2459 const ALIAS_BEST = 'bestalias';
2460 const ALIAS_FORLIFE = 'forlife';
2461 private $al = array();
2462 public function addAliasFilter($alias = null)
2463 {
2464 $this->requireAccounts();
2465 return $this->register_optional($this->al, $alias);
2466 }
2467
2468 protected function emailJoins()
2469 {
2470 global $globals;
2471 $joins = array();
2472 foreach ($this->e as $sub=>$key) {
2473 if (is_null($key)) {
2474 $joins['e' . $sub] = PlSqlJoin::left('emails', '$ME.uid = $UID AND $ME.flags != \'filter\'');
2475 } else {
2476 if (!is_array($key)) {
2477 $key = array($key);
2478 }
2479 $joins['e' . $sub] = PlSqlJoin::left('emails', '$ME.uid = $UID AND $ME.flags != \'filter\'
2480 AND $ME.email IN {?}', $key);
2481 }
2482 }
2483 foreach ($this->al as $sub=>$key) {
2484 if (is_null($key)) {
2485 $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\')');
2486 } else if ($key == self::ALIAS_BEST) {
2487 $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\') AND FIND_IN_SET(\'bestalias\', $ME.flags)');
2488 } else if ($key == self::ALIAS_FORLIFE) {
2489 $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type = \'a_vie\'');
2490 } else {
2491 if (!is_array($key)) {
2492 $key = array($key);
2493 }
2494 $joins['al' . $sub] = PlSqlJoin::left('aliases', '$ME.uid = $UID AND $ME.type IN (\'alias\', \'a_vie\')
2495 AND $ME.alias IN {?}', $key);
2496 }
2497 }
2498 foreach ($this->ve as $sub=>$key) {
2499 if (is_null($key)) {
2500 $joins['v' . $sub] = PlSqlJoin::left('virtual', '$ME.type = \'user\'');
2501 } else {
2502 if (!is_array($key)) {
2503 $key = array($key);
2504 }
2505 $joins['v' . $sub] = PlSqlJoin::left('virtual', '$ME.type = \'user\' AND $ME.alias IN {?}', $key);
2506 }
2507 $joins['vr' . $sub] = PlSqlJoin::left('virtual_redirect',
2508 '$ME.vid = v' . $sub . '.vid
2509 AND ($ME.redirect IN (CONCAT(al_forlife.alias, \'@\', {?}),
2510 CONCAT(al_forlife.alias, \'@\', {?}),
2511 a.email))',
2512 $globals->mail->domain, $globals->mail->domain2);
2513 }
2514 return $joins;
2515 }
2516
2517
2518 /** ADDRESSES
2519 */
2520 private $with_pa = false;
2521 public function addAddressFilter()
2522 {
2523 $this->requireProfiles();
2524 $this->with_pa = true;
2525 return 'pa';
2526 }
2527
2528 private $with_pac = false;
2529 public function addAddressCountryFilter()
2530 {
2531 $this->requireProfiles();
2532 $this->addAddressFilter();
2533 $this->with_pac = true;
2534 return 'gc';
2535 }
2536
2537 private $with_pal = false;
2538 public function addAddressLocalityFilter()
2539 {
2540 $this->requireProfiles();
2541 $this->addAddressFilter();
2542 $this->with_pal = true;
2543 return 'gl';
2544 }
2545
2546 protected function addressJoins()
2547 {
2548 $joins = array();
2549 if ($this->with_pa) {
2550 $joins['pa'] = PlSqlJoin::left('profile_addresses', '$ME.pid = $PID');
2551 }
2552 if ($this->with_pac) {
2553 $joins['gc'] = PlSqlJoin::left('geoloc_countries', '$ME.iso_3166_1_a2 = pa.countryID');
2554 }
2555 if ($this->with_pal) {
2556 $joins['gl'] = PlSqlJoin::left('geoloc_localities', '$ME.id = pa.localityID');
2557 }
2558 return $joins;
2559 }
2560
2561
2562 /** CORPS
2563 */
2564
2565 private $pc = false;
2566 private $pce = array();
2567 private $pcr = false;
2568 public function addCorpsFilter($type)
2569 {
2570 $this->requireProfiles();
2571 $this->pc = true;
2572 if ($type == UFC_Corps::CURRENT) {
2573 $pce['pcec'] = 'current_corpsid';
2574 return 'pcec';
2575 } else if ($type == UFC_Corps::ORIGIN) {
2576 $pce['pceo'] = 'original_corpsid';
2577 return 'pceo';
2578 }
2579 }
2580
2581 public function addCorpsRankFilter()
2582 {
2583 $this->requireProfiles();
2584 $this->pc = true;
2585 $this->pcr = true;
2586 return 'pcr';
2587 }
2588
2589 protected function corpsJoins()
2590 {
2591 $joins = array();
2592 if ($this->pc) {
2593 $joins['pc'] = PlSqlJoin::left('profile_corps', '$ME.pid = $PID');
2594 }
2595 if ($this->pcr) {
2596 $joins['pcr'] = PlSqlJoin::left('profile_corps_rank_enum', '$ME.id = pc.rankid');
2597 }
2598 foreach($this->pce as $sub => $field) {
2599 $joins[$sub] = PlSqlJoin::left('profile_corps_enum', '$ME.id = pc.' . $field);
2600 }
2601 return $joins;
2602 }
2603
2604 /** JOBS
2605 */
2606
2607 const JOB_USERDEFINED = 0x0001;
2608 const JOB_CV = 0x0002;
2609 const JOB_ANY = 0x0003;
2610
2611 /** Joins :
2612 * pj => profile_job
2613 * pje => profile_job_enum
2614 * pjt => profile_job_terms
2615 */
2616 private $with_pj = false;
2617 private $with_pje = false;
2618 private $with_pjt = 0;
2619
2620 public function addJobFilter()
2621 {
2622 $this->requireProfiles();
2623 $this->with_pj = true;
2624 return 'pj';
2625 }
2626
2627 public function addJobCompanyFilter()
2628 {
2629 $this->addJobFilter();
2630 $this->with_pje = true;
2631 return 'pje';
2632 }
2633
2634 /**
2635 * Adds a filter on job terms of profile.
2636 * @param $nb the number of job terms to use
2637 * @return an array of the fields to filter (one for each term).
2638 */
2639 public function addJobTermsFilter($nb = 1)
2640 {
2641 $this->with_pjt = $nb;
2642 $jobtermstable = array();
2643 for ($i = 1; $i <= $nb; ++$i) {
2644 $jobtermstable[] = 'pjtr_'.$i;
2645 }
2646 return $jobtermstable;
2647 }
2648
2649 protected function jobJoins()
2650 {
2651 $joins = array();
2652 if ($this->with_pj) {
2653 $joins['pj'] = PlSqlJoin::left('profile_job', '$ME.pid = $PID');
2654 }
2655 if ($this->with_pje) {
2656 $joins['pje'] = PlSqlJoin::left('profile_job_enum', '$ME.id = pj.jobid');
2657 }
2658 if ($this->with_pjt > 0) {
2659 for ($i = 1; $i <= $this->with_pjt; ++$i) {
2660 $joins['pjt_'.$i] = PlSqlJoin::left('profile_job_term', '$ME.pid = $PID');
2661 $joins['pjtr_'.$i] = PlSqlJoin::left('profile_job_term_relation', '$ME.jtid_2 = pjt_'.$i.'.jtid');
2662 }
2663 }
2664 return $joins;
2665 }
2666
2667 /** NETWORKING
2668 */
2669
2670 private $with_pnw = false;
2671 public function addNetworkingFilter()
2672 {
2673 $this->requireAccounts();
2674 $this->with_pnw = true;
2675 return 'pnw';
2676 }
2677
2678 protected function networkingJoins()
2679 {
2680 $joins = array();
2681 if ($this->with_pnw) {
2682 $joins['pnw'] = PlSqlJoin::left('profile_networking', '$ME.pid = $PID');
2683 }
2684 return $joins;
2685 }
2686
2687 /** PHONE
2688 */
2689
2690 private $with_ptel = false;
2691
2692 public function addPhoneFilter()
2693 {
2694 $this->requireAccounts();
2695 $this->with_ptel = true;
2696 return 'ptel';
2697 }
2698
2699 protected function phoneJoins()
2700 {
2701 $joins = array();
2702 if ($this->with_ptel) {
2703 $joins['ptel'] = PlSqlJoin::left('profile_phones', '$ME.pid = $PID');
2704 }
2705 return $joins;
2706 }
2707
2708 /** MEDALS
2709 */
2710
2711 private $with_pmed = false;
2712 public function addMedalFilter()
2713 {
2714 $this->requireProfiles();
2715 $this->with_pmed = true;
2716 return 'pmed';
2717 }
2718
2719 protected function medalJoins()
2720 {
2721 $joins = array();
2722 if ($this->with_pmed) {
2723 $joins['pmed'] = PlSqlJoin::left('profile_medals', '$ME.pid = $PID');
2724 }
2725 return $joins;
2726 }
2727
2728 /** MENTORING
2729 */
2730
2731 private $pms = array();
2732 private $mjtr = false;
2733 const MENTOR = 1;
2734 const MENTOR_EXPERTISE = 2;
2735 const MENTOR_COUNTRY = 3;
2736 const MENTOR_TERM = 4;
2737
2738 public function addMentorFilter($type)
2739 {
2740 $this->requireAccounts();
2741 switch($type) {
2742 case self::MENTOR:
2743 $this->pms['pm'] = 'profile_mentor';
2744 return 'pm';
2745 case self::MENTOR_EXPERTISE:
2746 $this->pms['pme'] = 'profile_mentor';
2747 return 'pme';
2748 case self::MENTOR_COUNTRY:
2749 $this->pms['pmc'] = 'profile_mentor_country';
2750 return 'pmc';
2751 case self::MENTOR_TERM:
2752 $this->pms['pmt'] = 'profile_mentor_term';
2753 $this->mjtr = true;
2754 return 'mjtr';
2755 default:
2756 Platal::page()->killError("Undefined mentor filter.");
2757 }
2758 }
2759
2760 protected function mentorJoins()
2761 {
2762 $joins = array();
2763 foreach ($this->pms as $sub => $tab) {
2764 $joins[$sub] = PlSqlJoin::left($tab, '$ME.pid = $PID');
2765 }
2766 if ($this->mjtr) {
2767 $joins['mjtr'] = PlSqlJoin::left('profile_job_term_relation', '$ME.jtid_2 = pmt.jtid');
2768 }
2769 return $joins;
2770 }
2771
2772 /** CONTACTS
2773 */
2774 private $cts = array();
2775 public function addContactFilter($uid = null)
2776 {
2777 $this->requireProfiles();
2778 return $this->register_optional($this->cts, is_null($uid) ? null : 'user_' . $uid);
2779 }
2780
2781 protected function contactJoins()
2782 {
2783 $joins = array();
2784 foreach ($this->cts as $sub=>$key) {
2785 if (is_null($key)) {
2786 $joins['c' . $sub] = PlSqlJoin::left('contacts', '$ME.contact = $PID');
2787 } else {
2788 $joins['c' . $sub] = PlSqlJoin::left('contacts', '$ME.uid = {?} AND $ME.contact = $PID', substr($key, 5));
2789 }
2790 }
2791 return $joins;
2792 }
2793
2794
2795 /** CARNET
2796 */
2797 private $wn = array();
2798 public function addWatchRegistrationFilter($uid = null)
2799 {
2800 $this->requireAccounts();
2801 return $this->register_optional($this->wn, is_null($uid) ? null : 'user_' . $uid);
2802 }
2803
2804 private $wp = array();
2805 public function addWatchPromoFilter($uid = null)
2806 {
2807 $this->requireAccounts();
2808 return $this->register_optional($this->wp, is_null($uid) ? null : 'user_' . $uid);
2809 }
2810
2811 private $w = array();
2812 public function addWatchFilter($uid = null)
2813 {
2814 $this->requireAccounts();
2815 return $this->register_optional($this->w, is_null($uid) ? null : 'user_' . $uid);
2816 }
2817
2818 protected function watchJoins()
2819 {
2820 $joins = array();
2821 foreach ($this->w as $sub=>$key) {
2822 if (is_null($key)) {
2823 $joins['w' . $sub] = PlSqlJoin::left('watch');
2824 } else {
2825 $joins['w' . $sub] = PlSqlJoin::left('watch', '$ME.uid = {?}', substr($key, 5));
2826 }
2827 }
2828 foreach ($this->wn as $sub=>$key) {
2829 if (is_null($key)) {
2830 $joins['wn' . $sub] = PlSqlJoin::left('watch_nonins', '$ME.ni_id = $UID');
2831 } else {
2832 $joins['wn' . $sub] = PlSqlJoin::left('watch_nonins', '$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5));
2833 }
2834 }
2835 foreach ($this->wn as $sub=>$key) {
2836 if (is_null($key)) {
2837 $joins['wn' . $sub] = PlSqlJoin::left('watch_nonins', '$ME.ni_id = $UID');
2838 } else {
2839 $joins['wn' . $sub] = PlSqlJoin::left('watch_nonins', '$ME.uid = {?} AND $ME.ni_id = $UID', substr($key, 5));
2840 }
2841 }
2842 foreach ($this->wp as $sub=>$key) {
2843 if (is_null($key)) {
2844 $joins['wp' . $sub] = PlSqlJoin::left('watch_promo');
2845 } else {
2846 $joins['wp' . $sub] = PlSqlJoin::left('watch_promo', '$ME.uid = {?}', substr($key, 5));
2847 }
2848 }
2849 return $joins;
2850 }
2851
2852
2853 /** PHOTOS
2854 */
2855 private $with_photo;
2856 public function addPhotoFilter()
2857 {
2858 $this->requireProfiles();
2859 $this->with_photo = true;
2860 return 'photo';
2861 }
2862
2863 protected function photoJoins()
2864 {
2865 if ($this->with_photo) {
2866 return array('photo' => PlSqlJoin::left('profile_photos', '$ME.pid = $PID'));
2867 } else {
2868 return array();
2869 }
2870 }
2871
2872
2873 /** MARKETING
2874 */
2875 private $with_rm;
2876 public function addMarketingHash()
2877 {
2878 $this->requireAccounts();
2879 $this->with_rm = true;
2880 }
2881
2882 protected function marketingJoins()
2883 {
2884 if ($this->with_rm) {
2885 return array('rm' => PlSqlJoin::left('register_marketing', '$ME.uid = $UID'));
2886 } else {
2887 return array();
2888 }
2889 }
2890 }
2891 // }}}
2892
2893 // {{{ class ProfileFilter
2894 class ProfileFilter extends UserFilter
2895 {
2896 public function get($limit = null)
2897 {
2898 return $this->getProfiles($limit);
2899 }
2900
2901 public function getIds($limit = null)
2902 {
2903 return $this->getPIDs();
2904 }
2905
2906 public function filter(array $profiles, $limit = null)
2907 {
2908 return $this->filterProfiles($profiles, self::defaultLimit($limit));
2909 }
2910
2911 public function getTotalCount()
2912 {
2913 return $this->getTotalProfileCount();
2914 }
2915
2916 public function getGroups()
2917 {
2918 return $this->getPIDGroups();
2919 }
2920 }
2921 // }}}
2922
2923 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
2924 ?>