Add ProfileMentoringSectors / ProfileMentoringCountries
[platal.git] / classes / profile.php
CommitLineData
e7b93962
FB
1<?php
2/***************************************************************************
d4c08d89 3 * Copyright (C) 2003-2010 Polytechnique.org *
e7b93962
FB
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
1a9affb7 22class ProfileVisibility
e7b93962 23{
1a9affb7
RB
24 static private $v_values = array(self::VIS_PUBLIC => array(self::VIS_PUBLIC),
25 self::VIS_AX => array(self::VIS_AX, self::VIS_PUBLIC),
26 self::VIS_PRIVATE => array(self::VIS_PRIVATE, self::VIS_AX, self::VIS_PUBLIC));
27
28 const VIS_PUBLIC = 'public';
29 const VIS_AX = 'ax';
30 const VIS_PRIVATE = 'private';
31
32 private $level;
913a4e90 33
1a9affb7
RB
34 public function __construct($level = null)
35 {
36 $this->setLevel($level);
37 }
38
39 public function setLevel($level = self::VIS_PUBLIC)
40 {
1f8250e4 41 if ($level != null && $level != self::VIS_PRIVATE && $level != self::VIS_AX && $level != self::VIS_PUBLIC) {
1a9affb7
RB
42 Platal::page()->kill("Invalid visibility: " . $level);
43 }
44
45 if (!S::logged()) {
46 $level = self::VIS_PUBLIC;
47 } else if ($level == null) {
48 $level = self::VIS_PRIVATE;
49 }
50
51 if ($this->level == null || $this->level == self::VIS_PRIVATE) {
52 $this->level = $level;
53 } else if ($this->level == self::VIS_AX && $level == self::VIS_PRIVATE) {
54 return;
55 } else {
56 $this->level = self::VIS_PUBLIC;
57 }
58 }
59
60 public function level()
61 {
62 if ($this->level == null) {
63 return self::VIS_PUBLIC;
64 } else {
65 return $this->level;
66 }
67 }
68
69 public function levels()
70 {
71 return self::$v_values[$this->level()];
72 }
73
74 public function isVisible($visibility)
75 {
76 return in_array($visibility, $this->levels());
77 }
78}
79
80class Profile
81{
f5642983 82
913a4e90
RB
83 /* name tokens */
84 const LASTNAME = 'lastname';
85 const FIRSTNAME = 'firstname';
86 const NICKNAME = 'nickname';
87 const PSEUDONYM = 'pseudonym';
88 const NAME = 'name';
89 /* name variants */
90 const VN_MARITAL = 'marital';
91 const VN_ORDINARY = 'ordinary';
92 const VN_OTHER = 'other';
93 const VN_INI = 'ini';
94 /* display names */
95 const DN_FULL = 'directory_name';
96 const DN_DISPLAY = 'yourself';
97 const DN_YOURSELF = 'yourself';
98 const DN_DIRECTORY = 'directory_name';
99 const DN_PRIVATE = 'private_name';
100 const DN_PUBLIC = 'public_name';
101 const DN_SHORT = 'short_name';
102 const DN_SORT = 'sort_name';
e02d9fbb
SJ
103 /* education related names */
104 const EDU_X = 'École polytechnique';
105 const DEGREE_X = 'Ingénieur';
106 const DEGREE_M = 'Master';
107 const DEGREE_D = 'Doctorat';
913a4e90
RB
108
109 static public $name_variants = array(
110 self::LASTNAME => array(self::VN_MARITAL, self::VN_ORDINARY),
111 self::FIRSTNAME => array(self::VN_ORDINARY, self::VN_INI, self::VN_OTHER)
112 );
113
f5642983
FB
114 const ADDRESS_MAIN = 0x000001;
115 const ADDRESS_PERSO = 0x000002;
116 const ADDRESS_PRO = 0x000004;
117 const ADDRESS_ALL = 0x000006;
118 const ADDRESS_POSTAL = 0x000008;
119
120 const EDUCATION_MAIN = 0x000010;
4bc5b8f0
FB
121 const EDUCATION_EXTRA = 0x000020;
122 const EDUCATION_ALL = 0x000040;
123 const EDUCATION_FINISHED = 0x000080;
124 const EDUCATION_CURRENT = 0x000100;
f5642983 125
4bc5b8f0
FB
126 const JOBS_MAIN = 0x001000;
127 const JOBS_ALL = 0x002000;
128 const JOBS_FINISHED = 0x004000;
129 const JOBS_CURRENT = 0x008000;
f5642983 130
04a94b1d
FB
131 const NETWORKING_ALL = 0x000000;
132 const NETWORKING_WEB = 0x010000;
133 const NETWORKING_IM = 0x020000;
134 const NETWORKING_SOCIAL = 0x040000;
135
4d1f0f6b
RB
136 const FETCH_ADDRESSES = 0x000001;
137 const FETCH_CORPS = 0x000002;
138 const FETCH_EDU = 0x000004;
139 const FETCH_JOBS = 0x000008;
140 const FETCH_MEDALS = 0x000010;
141 const FETCH_NETWORKING = 0x000020;
142 const FETCH_MENTOR_SECTOR = 0x000040;
143 const FETCH_MENTOR_COUNTRY = 0x000080;
144 const FETCH_PHONES = 0x000100;
9f21bd15 145
4d1f0f6b 146 const FETCH_MINIFICHES = 0x00012D; // FETCH_ADDRESSES | FETCH_EDU | FETCH_JOBS | FETCH_NETWORKING | FETCH_PHONES
fa9994fa 147
4d1f0f6b 148 const FETCH_ALL = 0x0001FF; // OR of FETCH_*
6b5008ec 149
1f8250e4
RB
150 private $fetched_fields = 0x000000;
151
e7b93962
FB
152 private $pid;
153 private $hrpid;
3e53a496
FB
154 private $data = array();
155
f5642983
FB
156 private $visibility = null;
157
1f8250e4 158
b774ddab 159 private function __construct(array $data)
e7b93962 160 {
b774ddab 161 $this->data = $data;
832e6fcb
FB
162 $this->pid = $this->data['pid'];
163 $this->hrpid = $this->data['hrpid'];
1a9affb7 164 $this->visibility = new ProfileVisibility();
9f21bd15
RB
165 }
166
e7b93962
FB
167 public function id()
168 {
169 return $this->pid;
170 }
171
172 public function hrid()
173 {
174 return $this->hrpid;
175 }
176
d5e60905
FB
177 public function promo()
178 {
179 return $this->promo;
180 }
181
845f0a4d
SJ
182 public function yearpromo()
183 {
184 return intval(substr($this->promo, 1, 4));
185 }
186
94b72319
FB
187 /** Print a name with the given formatting:
188 * %s = • for women
189 * %f = firstname
190 * %l = lastname
191 * %F = fullname
192 * %S = shortname
193 * %p = promo
194 */
195 public function name($format)
196 {
197 return str_replace(array('%s', '%f', '%l', '%F', '%S', '%p'),
198 array($this->isFemale() ? '•' : '',
faf75faf
SJ
199 $this->firstName(), $this->lastName(),
200 $this->fullName(), $this->shortName(),
201 $this->promo()), $format);
94b72319
FB
202 }
203
204 public function fullName($with_promo = false)
205 {
206 if ($with_promo) {
207 return $this->full_name . ' (' . $this->promo . ')';
208 }
209 return $this->full_name;
210 }
211
212 public function shortName($with_promo = false)
213 {
214 if ($with_promo) {
215 return $this->short_name . ' (' . $this->promo . ')';
216 }
217 return $this->short_name;
218 }
219
220 public function firstName()
221 {
08c91036 222 return $this->firstname;
94b72319
FB
223 }
224
5c005b37
RB
225 public function firstNames()
226 {
227 return $this->nameVariants(self::FIRSTNAME);
228 }
229
94b72319
FB
230 public function lastName()
231 {
08c91036 232 return $this->lastname;
94b72319
FB
233 }
234
5c005b37
RB
235 public function lastNames()
236 {
237 return $this->nameVariants(self::LASTNAME);
238 }
239
94b72319
FB
240 public function isFemale()
241 {
242 return $this->sex == PlUser::GENDER_FEMALE;
243 }
244
400ac338
RB
245 public function isDead()
246 {
247 return ($this->deathdate != null);
248 }
249
94b72319
FB
250 public function data()
251 {
252 $this->first_name;
253 return $this->data;
254 }
255
5c005b37
RB
256 private function nameVariants($type)
257 {
258 $vals = array($this->$type);
259 foreach (self::$name_variants[$type] as $var) {
260 $vartype = $type . '_' . $var;
261 $varname = $this->$vartype;
262 if ($varname != null && $varname != "") {
263 $vals[] = $varname;
264 }
265 }
266 return array_unique($vals);
267 }
268
b6569a95
FB
269 public function nationalities()
270 {
271 $nats = array();
272 if ($this->nationality1) {
273 $nats[] = $this->nationality1;
274 }
275 if ($this->nationality2) {
276 $nats[] = $this->nationality2;
277 }
278 if ($this->nationality3) {
279 $nats[] = $this->nationality3;
280 }
281 return $nats;
282 }
283
3e53a496
FB
284 public function __get($name)
285 {
286 if (property_exists($this, $name)) {
287 return $this->$name;
288 }
289
3e53a496
FB
290 if (isset($this->data[$name])) {
291 return $this->data[$name];
292 }
293
294 return null;
295 }
296
297 public function __isset($name)
298 {
299 return property_exists($this, $name) || isset($this->data[$name]);
300 }
301
c159e50f
RB
302 /** Sets the level of visibility of the profile
303 * Sets $this->visibility to a list of valid visibilities.
1a9affb7 304 * @param one of the self::VIS_* values
c159e50f 305 */
f5642983
FB
306 public function setVisibilityLevel($visibility)
307 {
1a9affb7 308 $this->visibility->setLevel($visibility);
f5642983
FB
309 }
310
c159e50f
RB
311 /** Determine whether an item with visibility $visibility can be displayed
312 * with the current level of visibility of the profile
313 * @param $visibility The level of visibility to be checked
314 */
315 public function isVisible($visibility)
316 {
1a9affb7 317 return $this->visibility->isVisible($visibility);
9f21bd15
RB
318 }
319
1f8250e4
RB
320 /** Stores the list of fields which have already been fetched for this Profile
321 */
322 public function setFetchedFields($fields)
990cb17b 323 {
1f8250e4
RB
324 if (($fields | self::FETCH_ALL) != self::FETCH_ALL) {
325 Platal::page()->kill("Invalid fetched fields: $fields");
326 }
327
328 $this->fetched_fields = $fields;
329 }
330
331 private function fetched($field)
332 {
333 if (!array_key_exists($field, ProfileField::$fields)) {
334 Platal::page()->kill("Invalid field: $field");
335 }
336
337 return ($this->fetched_fields & $field);
338 }
339
340 /** If not already done, fetches data for the given field
341 * @param $field One of the Profile::FETCH_*
342 * @return A ProfileField, or null
343 */
344 private function getProfileField($field)
345 {
346 if ($this->fetched($field)) {
347 return null;
348 } else {
349 $this->fetched_fields = $this->fetched_fields | $field;
350 }
351
352 $cls = ProfileField::$fields[$field];
353
990cb17b
RB
354 return ProfileField::getForPID($cls, $this->id(), $this->visibility);
355 }
356
8cf886dc
RB
357 /** Consolidates internal data (addresses, phones, jobs)
358 */
359 private function consolidateFields()
360 {
361 if ($this->phones != null) {
362 if ($this->addresses != null) {
363 $this->addresses->addPhones($this->phones);
364 }
365
366 if ($this->jobs != null) {
367 $this->jobs->addPhones($this->phones);
368 }
369 }
370
371 if ($this->addresses != null && $this->jobs != null) {
372 $this->jobs->addAddresses($this->addresses);
373 }
374 }
375
833a6e86
FB
376 /* Photo
377 */
1a9affb7 378 private $photo = null;
7988f7d6 379 public function getPhoto($fallback = true, $data = false)
9f21bd15 380 {
1a9affb7
RB
381 if ($this->has_photo) {
382 if ($data && ($this->photo == null || $this->photo->mimeType == null)) {
383 $res = XDB::fetchOneAssoc('SELECT attach, attachmime, x, y
1f8250e4
RB
384 FROM profile_photos
385 WHERE pid = {?}', $this->pid);
1a9affb7
RB
386 $this->photo = PlImage::fromData($res['attach'], $res['attachmime'], $res['x'], $res['y']);
387 } else if ($this->photo == null) {
388 $this->photo = PlImage::fromData(null, null, $this->photo_width, $this->photo_height);
389 }
390 return $this->photo;
9f21bd15
RB
391 } else if ($fallback) {
392 return PlImage::fromFile(dirname(__FILE__).'/../htdocs/images/none.png',
393 'image/png');
394 }
395 return null;
396 }
7d0ebdf5 397
4bc5b8f0
FB
398 /* Addresses
399 */
7d0ebdf5
RB
400 private $addresses = null;
401 public function setAddresses(ProfileAddresses $addr)
402 {
403 $this->addresses = $addr;
8cf886dc 404 $this->consolidateFields();
7d0ebdf5
RB
405 }
406
4bc5b8f0 407 public function getAddresses($flags, $limit = null)
f5642983 408 {
1f8250e4
RB
409 if ($this->addresses == null && !$this->fetched(self::FETCH_ADDRESSES)) {
410 $this->setAddresses($this->getProfileField(self::FETCH_ADDRESSES));
f5642983 411 }
0907501b
RB
412
413 if ($this->addresses == null) {
598c57f6 414 return array();
0907501b 415 }
990cb17b 416 return $this->addresses->get($flags, $limit);
f5642983
FB
417 }
418
598c57f6
RB
419 public function iterAddresses($flags, $limit = null)
420 {
421 return PlIteratorUtils::fromArray($this->getAddresses($flags, $limit), 1, true);
422 }
423
f5642983
FB
424 public function getMainAddress()
425 {
598c57f6
RB
426 $addr = $this->getAddresses(self::ADDRESS_PERSO | self::ADDRESS_MAIN);
427 if (count($addr) == 0) {
f5642983
FB
428 return null;
429 } else {
598c57f6 430 return array_pop($addr);
f5642983
FB
431 }
432 }
3e53a496 433
bdef0d33
RB
434 /* Phones
435 */
436 private $phones = null;
437 public function setPhones(ProfilePhones $phones)
438 {
439 $this->phones = $phones;
440 $this->consolidateFields();
441 }
442
443 public function getPhones($flags, $limit = null)
444 {
1f8250e4
RB
445 if ($this->phones == null && !$this->fetched(self::FETCH_PHONES)) {
446 $this->setPhones($this->getProfileField(self::FETCH_PHONES));
bdef0d33
RB
447 }
448
449 if ($this->phones == null) {
598c57f6 450 return array();
bdef0d33
RB
451 }
452 return $this->phones->get($flags, $limit);
453 }
4bc5b8f0
FB
454
455 /* Educations
456 */
d4d395bb 457 private $educations = null;
a060e1c3
RB
458 public function setEducations(ProfileEducation $edu)
459 {
460 $this->educations = $edu;
461 }
462
4bc5b8f0
FB
463 public function getEducations($flags, $limit = null)
464 {
1f8250e4
RB
465 if ($this->educations == null && !$this->fetched(self::FETCH_EDU)) {
466 $this->setEducations($this->getProfileField(self::FETCH_EDU));
d4d395bb 467 }
0907501b
RB
468
469 if ($this->educations == null) {
598c57f6 470 return array();
0907501b 471 }
a060e1c3 472 return $this->educations->get($flags, $limit);
4bc5b8f0
FB
473 }
474
475 public function getExtraEducations($limit = null)
476 {
477 return $this->getEducations(self::EDUCATION_EXTRA, $limit);
478 }
479
0396c259
RB
480 /* Corps
481 */
482 private $corps = null;
483 public function setCorps(ProfileCorps $corps)
484 {
485 $this->corps = $corps;
486 }
487
488 public function getCorps()
489 {
1f8250e4
RB
490 if ($this->corps == null && !$this->fetched(self::FETCH_CORPS)) {
491 $this->setCorps($this->getProfileField(self::FETCH_CORPS));
0396c259
RB
492 }
493 return $this->corps;
494 }
4bc5b8f0 495
04a94b1d
FB
496 /** Networking
497 */
d4d395bb
RB
498 private $networks = null;
499 public function setNetworking(ProfileNetworking $nw)
500 {
501 $this->networks = $nw;
502 }
04a94b1d
FB
503
504 public function getNetworking($flags, $limit = null)
505 {
1f8250e4
RB
506 if ($this->networks == null && !$this->fetched(self::FETCH_NETWORKING)) {
507 $this->setNetworking($this->getProfileField(self::FETCH_NETWORKING));
04a94b1d 508 }
0907501b 509 if ($this->networks == null) {
598c57f6 510 return array();
0907501b 511 }
d4d395bb 512 return $this->networks->get($flags, $limit);
04a94b1d
FB
513 }
514
515 public function getWebSite()
516 {
517 $site = $this->getNetworking(self::NETWORKING_WEB, 1);
598c57f6 518 if (count($site) != 1) {
04a94b1d
FB
519 return null;
520 }
598c57f6 521 $site = array_pop($site);
04a94b1d
FB
522 return $site['address'];
523 }
524
525
e718bd18
FB
526 /** Jobs
527 */
949fc736
RB
528 private $jobs = null;
529 public function setJobs(ProfileJobs $jobs)
530 {
531 $this->jobs = $jobs;
532 $this->consolidateFields();
533 }
e718bd18
FB
534
535 public function getJobs($flags, $limit = null)
536 {
1f8250e4
RB
537 if ($this->jobs == null && !$this->fetched(self::FETCH_JOBS)) {
538 $this->setJobs($this->getProfileField(self::FETCH_JOBS));
e718bd18 539 }
949fc736 540
0907501b 541 if ($this->jobs == null) {
598c57f6 542 return array();
0907501b 543 }
949fc736 544 return $this->jobs->get($flags, $limit);
e718bd18
FB
545 }
546
44ec5eb5 547 public function getMainJob()
e718bd18
FB
548 {
549 $job = $this->getJobs(self::JOBS_MAIN, 1);
598c57f6 550 if (count($job) != 1) {
e718bd18
FB
551 return null;
552 }
598c57f6 553 return array_pop($job);
e718bd18
FB
554 }
555
4d1f0f6b
RB
556 /* Mentoring
557 */
558 private $mentor_sectors = null;
559 public function setMentoringSectors(ProfileMentoringSectors $sectors)
560 {
561 $this->mentor_sectors = $sectors;
562 }
563
564 public function getMentoringSectors()
565 {
566 if ($this->mentor_sectors == null && !$this->fetched(self::FETCH_MENTOR_SECTOR)) {
567 $this->setMentoringSectors($this->getProfileField(self::FETCH_MENTOR_SECTOR));
568 }
569
570 if ($this->mentor_sectors == null) {
571 return array();
572 } else {
573 return $this->mentor_sectors->sectors;
574 }
575 }
576
577 private $mentor_countries = null;
578 public function setMentoringCountries(ProfileMentoringCountries $countries)
579 {
580 $this->mentor_countries = $countries;
581 }
582
583 public function getMentoringCountries()
584 {
585 if ($this->mentor_countries == null && !$this->fetched(self::FETCH_MENTOR_COUNTRY)) {
586 $this->setMentoringCountries($this->getProfileField(self::FETCH_MENTOR_COUNTRY));
587 }
588
589 if ($this->mentor_countries == null) {
590 return array();
591 } else {
592 return $this->mentor_countries->countries;
593 }
594 }
595
5c005b37
RB
596 /* Binets
597 */
598 public function getBinets()
599 {
600 return XDB::fetchColumn('SELECT binet_id
5c8a71f2 601 FROM profile_binets
bdd977d7 602 WHERE pid = {?}', $this->id());
5c005b37 603 }
97b71de5
RB
604 public function getBinetsNames()
605 {
606 return XDB::fetchColumn('SELECT text
607 FROM profile_binets AS pb
608 LEFT JOIN profile_binet_enum AS pbe ON (pbe.id = pb.binet_id)
609 WHERE pb.pid = {?}', $this->id());
610 }
5c005b37 611
26ca919b
RB
612 /* Medals
613 */
614 private $medals = null;
615 public function setMedals(ProfileMedals $medals)
616 {
617 $this->medals = $medals;
618 }
619
620 public function getMedals()
621 {
1f8250e4
RB
622 if ($this->medals == null && !$this->fetched(self::FETCH_MEDALS)) {
623 $this->setMedals($this->getProfileField(self::FETCH_MEDALS));
26ca919b
RB
624 }
625 if ($this->medals == null) {
626 return array();
627 }
628 return $this->medals->medals;
629 }
e718bd18 630
e7b93962
FB
631 public function owner()
632 {
633 return User::getSilent($this);
634 }
635
94590511
SJ
636 public function compareNames($firstname, $lastname)
637 {
638 $_lastname = mb_strtoupper($this->lastName());
639 $_firstname = mb_strtoupper($this->firstName());
640 $lastname = mb_strtoupper($lastname);
641 $firstname = mb_strtoupper($firstname);
642
643 $isOk = (mb_strtoupper($_firstname) == mb_strtoupper($firstname));
644 $tokens = preg_split("/[ \-']/", $lastname, -1, PREG_SPLIT_NO_EMPTY);
645 $maxlen = 0;
646
647 foreach ($tokens as $str) {
648 $isOk &= (strpos($_lastname, $str) !== false);
649 $maxlen = max($maxlen, strlen($str));
650 }
651
652 return ($isOk && ($maxlen > 2 || $maxlen == strlen($_lastname)));
653 }
654
9f21bd15 655 private static function fetchProfileData(array $pids, $respect_order = true, $fields = 0x0000, $visibility = null)
b774ddab
FB
656 {
657 if (count($pids) == 0) {
658 return array();
659 }
0d906109
RB
660
661 if ($respect_order) {
662 $order = 'ORDER BY ' . XDB::formatCustomOrder('p.pid', $pids);
663 } else {
664 $order = '';
665 }
666
1a9affb7 667 $visibility = new ProfileVisibility($visibility);
9f21bd15 668
97b71de5 669 $it = XDB::Iterator('SELECT p.*, p.sex = \'female\' AS sex, pe.entry_year, pe.grad_year, pse.text AS section,
9f21bd15
RB
670 pn_f.name AS firstname, pn_l.name AS lastname, pn_n.name AS nickname,
671 IF(pn_uf.name IS NULL, pn_f.name, pn_uf.name) AS firstname_ordinary,
672 IF(pn_ul.name IS NULL, pn_l.name, pn_ul.name) AS lastname_ordinary,
673 pd.promo AS promo, pd.short_name, pd.directory_name AS full_name,
1a9affb7
RB
674 pd.directory_name, IF(pp.pub IN {?}, pp.display_tel, NULL) AS mobile,
675 (ph.pub IN {?} AND ph.attach IS NOT NULL) AS has_photo,
7988f7d6 676 ph.x AS photo_width, ph.y AS photo_height,
9f21bd15 677 p.last_change < DATE_SUB(NOW(), INTERVAL 365 DAY) AS is_old,
bdef0d33 678 pm.expertise AS mentor_expertise,
9f21bd15
RB
679 ap.uid AS owner_id
680 FROM profiles AS p
681 INNER JOIN profile_display AS pd ON (pd.pid = p.pid)
682 INNER JOIN profile_education AS pe ON (pe.pid = p.pid AND FIND_IN_SET(\'primary\', pe.flags))
97b71de5 683 LEFT JOIN profile_section_enum AS pse ON (pse.id = p.section)
9f21bd15
RB
684 INNER JOIN profile_name AS pn_f ON (pn_f.pid = p.pid
685 AND pn_f.typeid = ' . self::getNameTypeId('firstname', true) . ')
686 INNER JOIN profile_name AS pn_l ON (pn_l.pid = p.pid
687 AND pn_l.typeid = ' . self::getNameTypeId('lastname', true) . ')
688 LEFT JOIN profile_name AS pn_uf ON (pn_uf.pid = p.pid
689 AND pn_uf.typeid = ' . self::getNameTypeId('firstname_ordinary', true) . ')
690 LEFT JOIN profile_name AS pn_ul ON (pn_ul.pid = p.pid
691 AND pn_ul.typeid = ' . self::getNameTypeId('lastname_ordinary', true) . ')
692 LEFT JOIN profile_name AS pn_n ON (pn_n.pid = p.pid
693 AND pn_n.typeid = ' . self::getNameTypeId('nickname', true) . ')
694 LEFT JOIN profile_phones AS pp ON (pp.pid = p.pid AND pp.link_type = \'user\' AND tel_type = \'mobile\')
695 LEFT JOIN profile_photos AS ph ON (ph.pid = p.pid)
bdef0d33 696 LEFT JOIN profile_mentor AS pm ON (pm.pid = p.pid)
9f21bd15
RB
697 LEFT JOIN account_profiles AS ap ON (ap.pid = p.pid AND FIND_IN_SET(\'owner\', ap.perms))
698 WHERE p.pid IN ' . XDB::formatArray($pids) . '
699 GROUP BY p.pid
1a9affb7 700 ' . $order, $visibility->levels(), $visibility->levels());
31ef12ef 701 return new ProfileIterator($it, $pids, $fields, $visibility);
b774ddab
FB
702 }
703
704 public static function getPID($login)
705 {
706 if ($login instanceof PlUser) {
707 return XDB::fetchOneCell('SELECT pid
708 FROM account_profiles
709 WHERE uid = {?} AND FIND_IN_SET(\'owner\', perms)',
710 $login->id());
9f21bd15 711 } else if (ctype_digit($login)) {
b774ddab
FB
712 return XDB::fetchOneCell('SELECT pid
713 FROM profiles
714 WHERE pid = {?}', $login);
715 } else {
716 return XDB::fetchOneCell('SELECT pid
717 FROM profiles
718 WHERE hrpid = {?}', $login);
719 }
720 }
721
0d906109
RB
722 public static function getPIDsFromUIDs($uids, $respect_order = true)
723 {
724 if ($respect_order) {
725 $order = 'ORDER BY ' . XDB::formatCustomOrder('uid', $uids);
726 } else {
727 $order = '';
728 }
729 return XDB::fetchAllAssoc('uid', 'SELECT ap.uid, ap.pid
730 FROM account_profiles AS ap
731 WHERE FIND_IN_SET(\'owner\', ap.perms)
9f21bd15
RB
732 AND ap.uid IN ' . XDB::formatArray($uids) .'
733 ' . $order);
0d906109 734 }
b774ddab 735
e7b93962
FB
736 /** Return the profile associated with the given login.
737 */
9f21bd15 738 public static function get($login, $fields = 0x0000, $visibility = null)
a3118782 739 {
641f2115 740 if (is_array($login)) {
1a9affb7
RB
741 $pf = new Profile($login);
742 $pf->setVisibilityLevel($visibility);
743 return $pf;
641f2115 744 }
b774ddab
FB
745 $pid = self::getPID($login);
746 if (!is_null($pid)) {
9f21bd15 747 $it = self::iterOverPIDs(array($pid), false, $fields, $visibility);
0d906109 748 return $it->next();
b774ddab 749 } else {
efe597c5
FB
750 /* Let say we can identify a profile using the identifiers of its owner.
751 */
455ea0c9
FB
752 if (!($login instanceof PlUser)) {
753 $user = User::getSilent($login);
754 if ($user && $user->hasProfile()) {
755 return $user->profile();
756 }
efe597c5 757 }
3e53a496 758 return null;
e7b93962
FB
759 }
760 }
a3118782 761
9f21bd15 762 public static function iterOverUIDs($uids, $respect_order = true, $fields = 0x0000, $visibility = null)
0d906109 763 {
9f21bd15 764 return self::iterOverPIDs(self::getPIDsFromUIDs($uids), $respect_order, $fields, $visibility);
0d906109
RB
765 }
766
9f21bd15 767 public static function iterOverPIDs($pids, $respect_order = true, $fields = 0x0000, $visibility = null)
0d906109 768 {
9f21bd15 769 return self::fetchProfileData($pids, $respect_order, $fields, $visibility);
0d906109
RB
770 }
771
b774ddab
FB
772 /** Return profiles for the list of pids.
773 */
1a9affb7 774 public static function getBulkProfilesWithPIDs(array $pids, $fields = 0x0000, $visibility = null)
b774ddab
FB
775 {
776 if (count($pids) == 0) {
777 return array();
778 }
9f21bd15 779 $it = self::iterOverPIDs($pids, true, $fields, $visibility);
b774ddab 780 $profiles = array();
0d906109
RB
781 while ($p = $it->next()) {
782 $profiles[$p->id()] = $p;
b774ddab
FB
783 }
784 return $profiles;
785 }
786
787 /** Return profiles for uids.
788 */
9f21bd15 789 public static function getBulkProfilesWithUIDS(array $uids, $fields = 0x000, $visibility = null)
b774ddab
FB
790 {
791 if (count($uids) == 0) {
792 return array();
793 }
9f21bd15 794 return self::getBulkProfilesWithPIDs(self::getPIDsFromUIDs($uids), $fields, $visibility);
b774ddab
FB
795 }
796
913a4e90
RB
797 public static function isDisplayName($name)
798 {
799 return $name == self::DN_FULL || $name == self::DN_DISPLAY
800 || $name == self::DN_YOURSELF || $name == self::DN_DIRECTORY
801 || $name == self::DN_PRIVATE || $name == self::DN_PUBLIC
802 || $name == self::DN_SHORT || $name == self::DN_SORT;
803 }
804
9f21bd15
RB
805 public static function getNameTypeId($type, $for_sql = false)
806 {
807 if (!S::has('name_types')) {
808 $table = XDB::fetchAllAssoc('type', 'SELECT id, type
809 FROM profile_name_enum');
810 S::set('name_types', $table);
811 } else {
812 $table = S::v('name_types');
813 }
814 if ($for_sql) {
815 return XDB::escape($table[$type]);
816 } else {
817 return $table[$type];
818 }
819 }
820
726eaf7a
SJ
821 public static function rebuildSearchTokens($pid)
822 {
823 XDB::execute('DELETE FROM search_name
6dbb167e 824 WHERE pid = {?}',
726eaf7a
SJ
825 $pid);
826 $keys = XDB::iterator("SELECT CONCAT(n.particle, n.name) AS name, e.score,
827 FIND_IN_SET('public', e.flags) AS public
828 FROM profile_name AS n
829 INNER JOIN profile_name_enum AS e ON (n.typeid = e.id)
830 WHERE n.pid = {?}",
831 $pid);
832
833 foreach ($keys as $i => $key) {
834 if ($key['name'] == '') {
835 continue;
836 }
837 $toks = preg_split('/[ \'\-]+/', $key['name']);
838 $token = '';
839 $first = 5;
840 while ($toks) {
841 $token = strtolower(replace_accent(array_pop($toks) . $token));
842 $score = ($toks ? 0 : 10 + $first) * ($key['score'] / 10);
6dbb167e 843 XDB::execute('REPLACE INTO search_name (token, pid, soundex, score, flags)
726eaf7a 844 VALUES ({?}, {?}, {?}, {?}, {?})',
6dbb167e 845 $token, $pid, soundex_fr($token), $score, $key['public']);
726eaf7a
SJ
846 $first = 0;
847 }
848 }
69b46857
SJ
849 }
850
851 /** The school identifier consists of 6 digits. The first 3 represent the
852 * promotion entry year. The last 3 indicate the student's rank.
853 *
854 * Our identifier consists of 8 digits and both half have the same role.
855 * This enables us to deal with bigger promotions and with a wider range
856 * of promotions.
857 *
858 * getSchoolId returns a school identifier given one of ours.
859 * getXorgId returns a X.org identifier given a school identifier.
860 */
861 public static function getSchoolId($xorgId)
862 {
863 if (!preg_match('/^[0-9]{8}$/', $xorgId)) {
864 return null;
865 }
866
867 $year = intval(substr($xorgId, 0, 4));
868 $rank = intval(substr($xorgId, 5, 3));
869 if ($year < 1996) {
870 return null;
871 } elseif ($year < 2000) {
872 $year = intval(substr(1900 - $year, 1, 3));
873 return sprintf('%02u0%03u', $year, $rank);
874 } else {
875 $year = intval(substr(1900 - $year, 1, 3));
876 return sprintf('%03u%03u', $year, $rank);
877 }
878 }
726eaf7a 879
69b46857
SJ
880 public static function getXorgId($schoolId)
881 {
94590511 882 if (!preg_match('/^[0-9]{6}$/', $schoolId)) {
a292484d
SJ
883 return null;
884 }
885
69b46857
SJ
886 $year = intval(substr($schoolId, 0, 3));
887 $rank = intval(substr($schoolId, 3, 3));
726eaf7a 888
69b46857
SJ
889 if ($year > 200) {
890 $year /= 10;
891 }
892 if ($year < 96) {
893 return null;
894 } else {
895 return sprintf('%04u%04u', 1900 + $year, $rank);
896 }
726eaf7a 897 }
e7b93962
FB
898}
899
31ef12ef
RB
900
901/** Iterator over a set of Profiles
902 */
903class ProfileIterator implements PlIterator
9f21bd15
RB
904{
905 private $iterator = null;
906 private $fields;
1a9affb7 907 private $visibility;
9f21bd15 908
1a9affb7 909 public function __construct(PlIterator $it, array $pids, $fields = 0x0000, ProfileVisibility $visibility = null)
9f21bd15
RB
910 {
911 require_once 'profilefields.inc.php';
1a9affb7
RB
912
913 if ($visibility == null) {
914 $visibility = new ProfileVisibility();
915 }
916
9f21bd15 917 $this->fields = $fields;
1a9affb7 918 $this->visibility = $visibility;
9f21bd15
RB
919
920 $subits = array();
921 $callbacks = array();
922
923 $subits[0] = $it;
924 $callbacks[0] = PlIteratorUtils::arrayValueCallback('pid');
925 $cb = PlIteratorUtils::objectPropertyCallback('pid');
926
927 if ($fields & Profile::FETCH_ADDRESSES) {
928 $callbacks[Profile::FETCH_ADDRESSES] = $cb;
929 $subits[Profile::FETCH_ADDRESSES] = new ProfileFieldIterator('ProfileAddresses', $pids, $visibility);
930 }
931
932 if ($fields & Profile::FETCH_CORPS) {
933 $callbacks[Profile::FETCH_CORPS] = $cb;
934 $subits[Profile::FETCH_CORPS] = new ProfileFieldIterator('ProfileCorps', $pids, $visibility);
935 }
936
937 if ($fields & Profile::FETCH_EDU) {
938 $callbacks[Profile::FETCH_EDU] = $cb;
939 $subits[Profile::FETCH_EDU] = new ProfileFieldIterator('ProfileEducation', $pids, $visibility);
940 }
941
942 if ($fields & Profile::FETCH_JOBS) {
943 $callbacks[Profile::FETCH_JOBS] = $cb;
944 $subits[Profile::FETCH_JOBS] = new ProfileFieldIterator('ProfileJobs', $pids, $visibility);
945 }
946
947 if ($fields & Profile::FETCH_MEDALS) {
948 $callbacks[Profile::FETCH_MEDALS] = $cb;
949 $subits[Profile::FETCH_MEDALS] = new ProfileFieldIterator('ProfileMedals', $pids, $visibility);
950 }
951
952 if ($fields & Profile::FETCH_NETWORKING) {
953 $callbacks[Profile::FETCH_NETWORKING] = $cb;
954 $subits[Profile::FETCH_NETWORKING] = new ProfileFieldIterator('ProfileNetworking', $pids, $visibility);
955 }
956
957 if ($fields & Profile::FETCH_PHONES) {
958 $callbacks[Profile::FETCH_PHONES] = $cb;
959 $subits[Profile::FETCH_PHONES] = new ProfileFieldIterator('ProfilePhones', $pids, $visibility);
960 }
961
9f21bd15
RB
962 $this->iterator = PlIteratorUtils::parallelIterator($subits, $callbacks, 0);
963 }
964
1f8250e4 965 private function hasData($field, $vals)
9f21bd15 966 {
1f8250e4 967 return ($this->fields & $field) && ($vals[$field] != null);
9f21bd15
RB
968 }
969
970 private function fillProfile(array $vals)
971 {
9f21bd15 972 $pf = Profile::get($vals[0]);
1a9affb7 973 $pf->setVisibilityLevel($this->visibility->level());
1f8250e4 974 $pf->setFetchedFields($this->fields);
1a9affb7 975
8cf886dc
RB
976 if ($this->hasData(Profile::FETCH_PHONES, $vals)) {
977 $pf->setPhones($vals[Profile::FETCH_PHONES]);
9f21bd15 978 }
8cf886dc
RB
979 if ($this->hasData(Profile::FETCH_ADDRESSES, $vals)) {
980 $pf->setAddresses($vals[Profile::FETCH_ADDRESSES]);
981 }
982 if ($this->hasData(Profile::FETCH_JOBS, $vals)) {
983 $pf->setJobs($vals[Profile::FETCH_JOBS]);
984 }
985
986 if ($this->hasData(Profile::FETCH_CORPS, $vals)) {
9f21bd15
RB
987 $pf->setCorps($vals[Profile::FETCH_CORPS]);
988 }
8cf886dc 989 if ($this->hasData(Profile::FETCH_EDU, $vals)) {
26ca919b 990 $pf->setEducations($vals[Profile::FETCH_EDU]);
9f21bd15 991 }
8cf886dc 992 if ($this->hasData(Profile::FETCH_MEDALS, $vals)) {
9f21bd15
RB
993 $pf->setMedals($vals[Profile::FETCH_MEDALS]);
994 }
8cf886dc 995 if ($this->hasData(Profile::FETCH_NETWORKING, $vals)) {
9f21bd15
RB
996 $pf->setNetworking($vals[Profile::FETCH_NETWORKING]);
997 }
9f21bd15
RB
998
999 return $pf;
1000 }
1001
1002 public function next()
1003 {
1004 $vals = $this->iterator->next();
1005 if ($vals == null) {
1006 return null;
1007 }
1008 return $this->fillProfile($vals);
1009 }
1010
1011 public function first()
1012 {
1013 return $this->iterator->first();
1014 }
1015
1016 public function last()
1017 {
1018 return $this->iterator->last();
1019 }
1020
1021 public function total()
1022 {
1023 return $this->iterator->total();
1024 }
1025}
1026
e7b93962
FB
1027// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
1028?>