Release plat/al core v1.1.13
[platal.git] / classes / pluser.php
CommitLineData
5421ab09
VZ
1<?php
2/***************************************************************************
e92ecb8c 3 * Copyright (C) 2003-2011 Polytechnique.org *
5421ab09
VZ
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 * PlUserNotFound is raised when a user id cannot be linked to a real user.
24 * The @p results give the list hruids (useful when several users are found).
25 */
26class UserNotFoundException extends Exception
27{
28 public function __construct($results = array())
29 {
30 $this->results = $results;
31 parent::__construct();
32 }
33}
34
9e394323
R
35interface PlUserInterface
36{
37 public static function _default_user_callback($login, $results);
38
39 /**
40 * Determines if the @p login is an email address, and an email address not
41 * served locally by plat/al.
42 */
43 public static function isForeignEmailAddress($email);
44}
45
5421ab09
VZ
46/**
47 * Represents an user of plat/al (without any further assumption), with a
48 * special focus on always-used properties (identification fields, display name,
49 * forlife/bestalias emails, ...).
50 * NOTE: each implementation of plat/al-code MUST subclass PlUser, and name it
51 * 'User'.
52 */
9e394323 53abstract class PlUser implements PlUserInterface
5421ab09
VZ
54{
55 /**
8829b862
VZ
56 * User data enumerations.
57 */
58 const GENDER_FEMALE = true;
59 const GENDER_MALE = false;
60 const FORMAT_HTML = "html";
61 const FORMAT_TEXT = "text";
62
63 /**
5421ab09
VZ
64 * User data storage.
65 * By convention, null means the information hasn't been fetched yet, and
66 * false means the information is not available.
67 */
6c36b907 68
643ae90b 69 // uid is internal user ID (potentially numeric), whereas hruid is a
6c36b907 70 // "human readable" unique ID
643ae90b 71 protected $uid = null;
5421ab09
VZ
72 protected $hruid = null;
73
74 // User main email aliases (forlife is the for-life email address, bestalias
3f0fafbd
RB
75 // is user-chosen preferred email address, email might be any email available
76 // for the user).
5421ab09
VZ
77 protected $forlife = null;
78 protected $bestalias = null;
3f0fafbd 79 protected $email = null;
5421ab09
VZ
80
81 // Display name is user-chosen name to display (eg. in "Welcome
82 // <display name> !"), while full name is the official full name.
83 protected $display_name = null;
84 protected $full_name = null;
c16ab9ce 85 protected $sort_name = null;
5421ab09 86
8829b862
VZ
87 // Other important parameters used when sending emails.
88 protected $gender = null; // Acceptable values are GENDER_MALE and GENDER_FEMALE
89 protected $email_format = null; // Acceptable values are FORMAT_HTML and FORMAT_TEXT
90
f8b161ad
FB
91 // Permissions
92 protected $perms = null;
93 protected $perm_flags = null;
94
5421ab09
VZ
95 // Other properties are listed in this key-value hash map.
96 protected $data = array();
97
98 /**
99 * Constructs the PlUser object from an identifier (any identifier which is
100 * understood by getLogin() implementation).
101 *
102 * @param $login An user login.
103 * @param $values List of known user properties.
104 */
105 public function __construct($login, $values = array())
106 {
107 $this->fillFromArray($values);
108
109 // If the user id was not part of the known values, determines it from
110 // the login.
643ae90b
SJ
111 if (!$this->uid) {
112 $this->uid = $this->getLogin($login);
5421ab09
VZ
113 }
114
115 // Preloads main properties (assumes the loader will lazily get them
116 // from variables already set in the object).
117 $this->loadMainFields();
118 }
119
120 /**
121 * Get the canonical user id for the @p login.
122 *
123 * @param $login An user login.
124 * @return The canonical user id.
125 * @throws UserNotFoundException when login is not found.
126 */
127 abstract protected function getLogin($login);
128
129 /**
130 * Loads the main properties (hruid, forlife, bestalias, ...) from the
131 * database. Should return immediately when the properties are already
132 * available.
133 */
134 abstract protected function loadMainFields();
135
136 /**
137 * Accessors to the main properties, ie. those available as top level
138 * object variables.
139 */
f8b161ad
FB
140 public function id()
141 {
643ae90b 142 return $this->uid;
f8b161ad 143 }
5421ab09 144
f8b161ad
FB
145 public function login()
146 {
147 return $this->hruid;
148 }
5421ab09 149
792e4607
FB
150 public function isMe($other)
151 {
152 if (!$other) {
153 return false;
154 } else if ($other instanceof PlUser) {
155 return $other->id() == $this->id();
156 } else {
157 return false;
158 }
159 }
160
f8b161ad
FB
161 public function bestEmail()
162 {
f992b7a5
PC
163 if (!empty($this->bestalias)) {
164 return $this->bestalias;
165 }
166 return $this->email;
f8b161ad
FB
167 }
168 public function forlifeEmail()
169 {
f992b7a5
PC
170 if (!empty($this->forlife)) {
171 return $this->forlife;
172 }
173 return $this->email;
f8b161ad
FB
174 }
175
176 public function displayName()
177 {
178 return $this->display_name;
179 }
180 public function fullName()
181 {
182 return $this->full_name;
183 }
5421ab09 184
35fff9b0
FB
185 abstract public function password();
186
8829b862
VZ
187 // Fallback value is GENDER_MALE.
188 public function isFemale()
189 {
190 return $this->gender == self::GENDER_FEMALE;
191 }
192
193 // Fallback value is FORMAT_TEXT.
194 public function isEmailFormatHtml()
195 {
196 return $this->email_format == self::FORMAT_HTML;
197 }
198
5421ab09
VZ
199 /**
200 * Other properties are available directly through the $data array, or as
201 * standard object variables, using a getter.
202 */
f8b161ad
FB
203 public function data()
204 {
205 return $this->data;
206 }
5421ab09
VZ
207
208 public function __get($name)
209 {
9ddc36c1 210 if (property_exists($this, $name)) {
5421ab09
VZ
211 return $this->$name;
212 }
213
214 if (isset($this->data[$name])) {
215 return $this->data[$name];
216 }
217
218 return null;
219 }
220
221 public function __isset($name)
222 {
9ddc36c1 223 return property_exists($this, $name) || isset($this->data[$name]);
5421ab09
VZ
224 }
225
fd05958d
FB
226 public function __unset($name)
227 {
228 if (property_exists($this, $name)) {
229 $this->$name = null;
230 } else {
231 unset($this->data[$name]);
232 }
233 }
234
5421ab09
VZ
235 /**
236 * Fills the object properties using the @p associative array; the intended
237 * user case is to fill the object using SQL obtained arrays.
238 *
239 * @param $values Key-value array of user properties.
240 */
241 protected function fillFromArray(array $values)
242 {
243 // Merge main properties with existing ones.
244 unset($values['data']);
245 foreach ($values as $key => $value) {
246 if (property_exists($this, $key) && !isset($this->$key)) {
247 $this->$key = $value;
248 }
249 }
250
251 // Merge all value into the $this->data placeholder.
252 $this->data = array_merge($this->data, $values);
253 }
254
9ddc36c1
VZ
255 /**
256 * Adds properties to the object; this method does not allow the caller to
257 * update core properties (id, ...).
258 *
259 * @param $values An associative array of non-core properties.
260 */
261 public function addProperties(array $values)
262 {
263 foreach ($values as $key => $value) {
264 if (!property_exists($this, $key)) {
265 $this->data[$key] = $value;
266 }
267 }
268 }
269
5421ab09
VZ
270
271 /**
f8b161ad
FB
272 * Build the permissions flags for the user.
273 */
274 abstract protected function buildPerms();
275
276 /**
277 * Check wether the user got the given permission combination.
278 */
279 public function checkPerms($perms)
280 {
281 if (is_null($this->perm_flags)) {
282 $this->buildPerms();
283 }
284 if (is_null($this->perm_flags)) {
285 return false;
286 }
287 return $this->perm_flags->hasFlagCombination($perms);
288 }
289
290
291 /**
5421ab09
VZ
292 * Returns a valid User object built from the @p id and optionnal @p values,
293 * or returns false and calls the callback if the @p id is not valid.
294 */
295 public static function get($login, $callback = false)
296 {
297 return User::getWithValues($login, array(), $callback);
298 }
299
300 public static function getWithValues($login, $values, $callback = false)
301 {
302 if (!$callback) {
303 $callback = array('User', '_default_user_callback');
304 }
305
306 try {
307 return new User($login, $values);
308 } catch (UserNotFoundException $e) {
309 return call_user_func($callback, $login, $e->results);
310 }
311 }
312
933e1c23
FB
313 public static function getWithUID($uid, $callback = false)
314 {
643ae90b 315 return User::getWithValues(null, array('uid' => $uid), $callback);
933e1c23
FB
316 }
317
5421ab09
VZ
318 // Same as above, but using the silent callback as default.
319 public static function getSilent($login)
320 {
321 return User::getWithValues($login, array(), array('User', '_silent_user_callback'));
322 }
323
324 public static function getSilentWithValues($login, $values)
325 {
326 return User::getWithValues($login, $values, array('User', '_silent_user_callback'));
327 }
328
933e1c23
FB
329 public static function getSilentWithUID($uid)
330 {
643ae90b 331 return User::getWithValues(null, array('uid' => $uid), array('User', '_silent_user_callback'));
933e1c23
FB
332 }
333
5421ab09 334 /**
40d6b19a
VZ
335 * Retrieves User objects corresponding to the @p logins, and eventually
336 * extracts and returns the @p property. If @p strict mode is disabled, it
337 * also includes logins for which no forlife was found (but it still calls
338 * the callback for them).
5421ab09
VZ
339 * In all cases, email addresses which are not from the local domains are
340 * kept.
341 *
342 * @param $logins Array of user logins.
40d6b19a 343 * @param $property Property to retrieve from the User objects.
5421ab09
VZ
344 * @param $strict Should unvalidated logins be returned as-is or discarded ?
345 * @param $callback Callback to call when a login is unknown to the system.
346 * @return Array of validated user forlife emails.
347 */
40d6b19a 348 private static function getBulkUserProperties($logins, $property, $strict, $callback)
5421ab09
VZ
349 {
350 if (!is_array($logins)) {
351 if (strlen(trim($logins)) == 0) {
352 return null;
353 }
9e394323 354 $logins = preg_split("/[; ,\r\n\|]+/", $logins);
5421ab09
VZ
355 }
356
357 if ($logins) {
358 $list = array();
7446ea51 359 foreach ($logins as $i => $login) {
5421ab09
VZ
360 $login = trim($login);
361 if (empty($login)) {
362 continue;
363 }
364
365 if (($user = User::get($login, $callback))) {
7446ea51 366 $list[$i] = $user->$property();
b9ca76f9 367 } else if (!$strict || (User::isForeignEmailAddress($login) && isvalid_email($login))) {
7446ea51 368 $list[$i] = $login;
5421ab09
VZ
369 }
370 }
45678332 371 return array_unique($list);
5421ab09
VZ
372 }
373 return null;
374 }
375
376 /**
40d6b19a
VZ
377 * Returns hruid corresponding to the @p logins. See getBulkUserProperties()
378 * for details.
379 */
380 public static function getBulkHruid($logins, $callback = false)
381 {
382 return self::getBulkUserProperties($logins, 'login', true, $callback);
383 }
384
385 /**
386 * Returns forlife emails corresponding to the @p logins. See
387 * getBulkUserProperties() for details.
388 */
389 public static function getBulkForlifeEmails($logins, $strict = true, $callback = false)
390 {
391 return self::getBulkUserProperties($logins, 'forlifeEmail', $strict, $callback);
392 }
393
394 /**
5421ab09
VZ
395 * Predefined callbacks for the user lookup; they are called when a given
396 * login is found not to be associated with any valid user. Silent callback
397 * does nothing; default callback is supposed to display an error message,
398 * using the Platal::page() hook.
399 */
400 public static function _silent_user_callback($login, $results)
401 {
402 return;
403 }
404
cdf1c82b
SJ
405 private static function stripBadChars($text)
406 {
0073f7e2 407 return str_replace(array(' ', "'", '+'), array('-', '', '_'),
a95dcb6e 408 strtolower(stripslashes(replace_accent(trim($text)))));
cdf1c82b
SJ
409 }
410
411 /** Creates a username from a first and last name
412 * @param $firstname User's firstname
413 * @param $lasttname User's lastname
414 * return STRING the corresponding username
415 */
2a683f7f 416 public static function makeUserName($firstname, $lastname)
cdf1c82b
SJ
417 {
418 return self::stripBadChars($firstname) . '.' . self::stripBadChars($lastname);
419 }
420
0be5c71c
SJ
421 /**
422 * Creates a user forlive identifier from:
423 * @param $firstname User's firstname
424 * @param $lasttname User's lastname
425 * @param $category User's promotion or type of account
426 */
427 public static function makeHrid($firstname, $lastname, $category)
428 {
cdf1c82b 429 $cat = self::stripBadChars($category);
16231685 430 if (!$cat) {
cdf1c82b
SJ
431 Platal::page()->kill("$category is not a suitable category.");
432 }
433
434 return self::makeUserName($firstname, $lastname) . '.' . $cat;
0be5c71c
SJ
435 }
436
a95dcb6e
SJ
437 /** Reformats the firstname so that all letters are in lower case,
438 * except the first letter of each part of the name.
439 */
440 public static function fixFirstnameCase($firstname)
441 {
442 $firstname = strtolower($firstname);
443 $pieces = explode('-', $firstname);
444
445 foreach ($pieces as $piece) {
446 $subpieces = explode("'", $piece);
447 $usubpieces = '';
0be5c71c 448
a95dcb6e
SJ
449 foreach ($subpieces as $subpiece) {
450 $usubpieces[] = ucwords($subpiece);
451 }
452 $upieces[] = implode("'", $usubpieces);
453 }
454 return implode('-', $upieces);
455 }
5421ab09
VZ
456}
457
fa7ffd66 458// vim:set et sw=4 sts=4 sws=4 foldmethod=marker fenc=utf-8:
5421ab09 459?>