ef9c58fdd5985aa615bd6c43bd0efbb78996c980
[platal.git] / classes / pluser.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2008 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 * 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 */
26 class UserNotFoundException extends Exception
27 {
28 public function __construct($results = array())
29 {
30 $this->results = $results;
31 parent::__construct();
32 }
33 }
34
35 /**
36 * Represents an user of plat/al (without any further assumption), with a
37 * special focus on always-used properties (identification fields, display name,
38 * forlife/bestalias emails, ...).
39 * NOTE: each implementation of plat/al-code MUST subclass PlUser, and name it
40 * 'User'.
41 */
42 abstract class PlUser
43 {
44 /**
45 * User data enumerations.
46 */
47 const GENDER_FEMALE = true;
48 const GENDER_MALE = false;
49 const FORMAT_HTML = "html";
50 const FORMAT_TEXT = "text";
51
52 /**
53 * User data storage.
54 * By convention, null means the information hasn't been fetched yet, and
55 * false means the information is not available.
56 */
57 protected $user_id = null;
58 protected $hruid = null;
59
60 // User main email aliases (forlife is the for-life email address, bestalias
61 // is user-chosen preferred email address).
62 protected $forlife = null;
63 protected $bestalias = null;
64
65 // Display name is user-chosen name to display (eg. in "Welcome
66 // <display name> !"), while full name is the official full name.
67 protected $display_name = null;
68 protected $full_name = null;
69 protected $promo = null;
70
71 // Other important parameters used when sending emails.
72 protected $gender = null; // Acceptable values are GENDER_MALE and GENDER_FEMALE
73 protected $email_format = null; // Acceptable values are FORMAT_HTML and FORMAT_TEXT
74
75 // Permissions
76 protected $perms = null;
77 protected $perm_flags = null;
78
79 // Other properties are listed in this key-value hash map.
80 protected $data = array();
81
82 /**
83 * Constructs the PlUser object from an identifier (any identifier which is
84 * understood by getLogin() implementation).
85 *
86 * @param $login An user login.
87 * @param $values List of known user properties.
88 */
89 public function __construct($login, $values = array())
90 {
91 $this->fillFromArray($values);
92
93 // If the user id was not part of the known values, determines it from
94 // the login.
95 if (!$this->user_id) {
96 $this->user_id = $this->getLogin($login);
97 }
98
99 // Preloads main properties (assumes the loader will lazily get them
100 // from variables already set in the object).
101 $this->loadMainFields();
102 }
103
104 /**
105 * Get the canonical user id for the @p login.
106 *
107 * @param $login An user login.
108 * @return The canonical user id.
109 * @throws UserNotFoundException when login is not found.
110 */
111 abstract protected function getLogin($login);
112
113 /**
114 * Loads the main properties (hruid, forlife, bestalias, ...) from the
115 * database. Should return immediately when the properties are already
116 * available.
117 */
118 abstract protected function loadMainFields();
119
120 /**
121 * Accessors to the main properties, ie. those available as top level
122 * object variables.
123 */
124 public function id()
125 {
126 return $this->user_id;
127 }
128
129 public function login()
130 {
131 return $this->hruid;
132 }
133
134 public function bestEmail()
135 {
136 return $this->bestalias;
137 }
138 public function forlifeEmail()
139 {
140 return $this->forlife;
141 }
142
143 public function displayName()
144 {
145 return $this->display_name;
146 }
147 public function fullName()
148 {
149 return $this->full_name;
150 }
151
152 public function promo()
153 {
154 return $this->promo;
155 }
156
157 abstract public function password();
158
159 // Fallback value is GENDER_MALE.
160 public function isFemale()
161 {
162 return $this->gender == self::GENDER_FEMALE;
163 }
164
165 // Fallback value is FORMAT_TEXT.
166 public function isEmailFormatHtml()
167 {
168 return $this->email_format == self::FORMAT_HTML;
169 }
170
171 /**
172 * Other properties are available directly through the $data array, or as
173 * standard object variables, using a getter.
174 */
175 public function data()
176 {
177 return $this->data;
178 }
179
180 public function __get($name)
181 {
182 if (property_exists($this, $name)) {
183 return $this->$name;
184 }
185
186 if (isset($this->data[$name])) {
187 return $this->data[$name];
188 }
189
190 return null;
191 }
192
193 public function __isset($name)
194 {
195 return property_exists($this, $name) || isset($this->data[$name]);
196 }
197
198 /**
199 * Fills the object properties using the @p associative array; the intended
200 * user case is to fill the object using SQL obtained arrays.
201 *
202 * @param $values Key-value array of user properties.
203 */
204 protected function fillFromArray(array $values)
205 {
206 // Merge main properties with existing ones.
207 unset($values['data']);
208 foreach ($values as $key => $value) {
209 if (property_exists($this, $key) && !isset($this->$key)) {
210 $this->$key = $value;
211 }
212 }
213
214 // Merge all value into the $this->data placeholder.
215 $this->data = array_merge($this->data, $values);
216 }
217
218 /**
219 * Adds properties to the object; this method does not allow the caller to
220 * update core properties (id, ...).
221 *
222 * @param $values An associative array of non-core properties.
223 */
224 public function addProperties(array $values)
225 {
226 foreach ($values as $key => $value) {
227 if (!property_exists($this, $key)) {
228 $this->data[$key] = $value;
229 }
230 }
231 }
232
233
234 /**
235 * Build the permissions flags for the user.
236 */
237 abstract protected function buildPerms();
238
239 /**
240 * Check wether the user got the given permission combination.
241 */
242 public function checkPerms($perms)
243 {
244 if (is_null($this->perm_flags)) {
245 $this->buildPerms();
246 }
247 if (is_null($this->perm_flags)) {
248 return false;
249 }
250 return $this->perm_flags->hasFlagCombination($perms);
251 }
252
253
254 /**
255 * Returns a valid User object built from the @p id and optionnal @p values,
256 * or returns false and calls the callback if the @p id is not valid.
257 */
258 public static function get($login, $callback = false)
259 {
260 return User::getWithValues($login, array(), $callback);
261 }
262
263 public static function getWithValues($login, $values, $callback = false)
264 {
265 if (!$callback) {
266 $callback = array('User', '_default_user_callback');
267 }
268
269 try {
270 return new User($login, $values);
271 } catch (UserNotFoundException $e) {
272 return call_user_func($callback, $login, $e->results);
273 }
274 }
275
276 public static function getWithUID($uid, $callback = false)
277 {
278 return User::getWithValues(null, array('user_id' => $uid), $callback);
279 }
280
281 // Same as above, but using the silent callback as default.
282 public static function getSilent($login)
283 {
284 return User::getWithValues($login, array(), array('User', '_silent_user_callback'));
285 }
286
287 public static function getSilentWithValues($login, $values)
288 {
289 return User::getWithValues($login, $values, array('User', '_silent_user_callback'));
290 }
291
292 public static function getSilentWithUID($uid)
293 {
294 return User::getWithValues($uid, array('User', '_silent_user_callback'));
295 }
296
297 /**
298 * Retrieves User objects corresponding to the @p logins, and eventually
299 * extracts and returns the @p property. If @p strict mode is disabled, it
300 * also includes logins for which no forlife was found (but it still calls
301 * the callback for them).
302 * In all cases, email addresses which are not from the local domains are
303 * kept.
304 *
305 * @param $logins Array of user logins.
306 * @param $property Property to retrieve from the User objects.
307 * @param $strict Should unvalidated logins be returned as-is or discarded ?
308 * @param $callback Callback to call when a login is unknown to the system.
309 * @return Array of validated user forlife emails.
310 */
311 private static function getBulkUserProperties($logins, $property, $strict, $callback)
312 {
313 if (!is_array($logins)) {
314 if (strlen(trim($logins)) == 0) {
315 return null;
316 }
317 $logins = split("[; ,\r\n\|]+", $logins);
318 }
319
320 if ($logins) {
321 $list = array();
322 foreach ($logins as $i => $login) {
323 $login = trim($login);
324 if (empty($login)) {
325 continue;
326 }
327
328 if (($user = User::get($login, $callback))) {
329 $list[$i] = $user->$property();
330 } else if (!$strict || User::isForeignEmailAddress($login)) {
331 $list[$i] = $login;
332 }
333 }
334 return $list;
335 }
336 return null;
337 }
338
339 /**
340 * Returns hruid corresponding to the @p logins. See getBulkUserProperties()
341 * for details.
342 */
343 public static function getBulkHruid($logins, $callback = false)
344 {
345 return self::getBulkUserProperties($logins, 'login', true, $callback);
346 }
347
348 /**
349 * Returns forlife emails corresponding to the @p logins. See
350 * getBulkUserProperties() for details.
351 */
352 public static function getBulkForlifeEmails($logins, $strict = true, $callback = false)
353 {
354 return self::getBulkUserProperties($logins, 'forlifeEmail', $strict, $callback);
355 }
356
357 /**
358 * Predefined callbacks for the user lookup; they are called when a given
359 * login is found not to be associated with any valid user. Silent callback
360 * does nothing; default callback is supposed to display an error message,
361 * using the Platal::page() hook.
362 */
363 public static function _silent_user_callback($login, $results)
364 {
365 return;
366 }
367
368 abstract public static function _default_user_callback($login, $results);
369
370 /**
371 * Determines if the @p login is an email address, and an email address not
372 * served locally by plat/al.
373 */
374 abstract public static function isForeignEmailAddress($email);
375 }
376
377 // vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
378 ?>