bug 1391: Fix Whitespace
[cacert-mgr.git] / manager / application / models / User.php
1 <?php
2 /**
3 * @author Michael Tänzer
4 */
5
6 class Default_Model_User {
7 protected $db;
8
9 protected $id;
10 protected $points = null;
11
12 protected function __construct(Zend_Db_Adapter_Abstract $db, $id) {
13 // Not allowed to create new users from within the manager
14
15 $this->db = $db;
16 $this->id = $id;
17 }
18
19 /**
20 * Get an user object for the given ID
21 *
22 * @param $id int
23 * @return Default_Model_User
24 */
25 public static function findById($id) {
26 // Get database connection
27 $config = new Zend_Config_Ini(
28 APPLICATION_PATH . '/configs/application.ini',
29 APPLICATION_ENV);
30 $db = Zend_Db::factory($config->ca_mgr->db->auth->pdo,
31 $config->ca_mgr->db->auth);
32
33 // Check if the ID is present on the test server
34 $query = 'select `id` from `users` where `id` = :user';
35 $query_params['user'] = $id;
36 $result = $db->query($query, $query_params);
37 if ($result->rowCount() !== 1) {
38 throw new Exception(
39 __METHOD__ . ': user ID not found in the data base');
40 }
41 $row = $result->fetch();
42
43 return new Default_Model_User($db, $row['id']);
44 }
45
46 /**
47 * Get an user object for the currently logged in user
48 *
49 * @return Default_Model_User
50 */
51 public static function findCurrentUser() {
52 $session = Zend_Registry::get('session');
53 if ($session->authdata['authed'] !== true) {
54 throw new Exception(
55 __METHOD__ . ': you need to log in to use this feature');
56 }
57
58 return self::findById($session->authdata['authed_id']);
59 }
60
61 /**
62 * Get the first assurer who didn't already assure the user
63 *
64 * @return Default_Model_User
65 */
66 public function findNewAssurer()
67 {
68 $query = 'select min(`id`) as `assurer` from `users` ' .
69 'where `email` like \'john.doe-___@example.com\' and ' .
70 '`id` not in (select `from` from `notary` where `to` = :user)';
71 $query_params['user'] = $this->id;
72 $row = $this->db->query($query, $query_params)->fetch();
73
74 if ($row['assurer'] === NULL) {
75 throw new Exception(
76 __METHOD__ . ': no more assurers that haven\'t already '.
77 'assured this account');
78 }
79
80 return new Default_Model_User($this->db, $row['assurer']);
81 }
82
83 /**
84 * Get the first assuree who hasn't already been assured by this user
85 *
86 * @return Default_Model_User
87 */
88 public function findNewAssuree() {
89 $query = 'select min(`id`) as `assuree` from `users` ' .
90 'where `email` like \'john.doe-___@example.com\' and ' .
91 '`id` not in (select `to` from `notary` where `from` = :user)';
92 $query_params['user'] = $this->id;
93 $row = $this->db->query($query, $query_params)->fetch();
94
95 if ($row['assuree'] === NULL) {
96 throw new Exception(
97 __METHOD__ . ': no more assurees that haven\'t already '.
98 'been assured by this account');
99 }
100
101 return new Default_Model_User($this->db, $row['assuree']);
102 }
103
104 /**
105 * Refresh the current value of points from the test server
106 *
107 * Needed if operations outside this class are made, that might affect the
108 * user's points
109 */
110 public function refreshPoints() {
111 $query = "select sum(`points`) as `total` from `notary` " .
112 "where `to` = :user and method != 'Administrative Increase' and from != to";
113 $query_params['user'] = $this->id;
114 $row = $this->db->query($query, $query_params)->fetch();
115 if ($row['total'] === null) $row['total'] = 0;
116
117 $this->points = $row['total'];
118 }
119
120 /**
121 * Get points of the user
122 *
123 * @return int
124 * The amount of points the user has
125 */
126 public function getPoints()
127 {
128 if ($this->points === null) {
129 $this->refreshPoints();
130 }
131
132 return $this->points;
133 }
134
135 /**
136 * Fix the assurer flag for the user
137 */
138 public function fixAssurerFlag()
139 {
140 // TODO: unset flag if requirements are not met
141
142 $query = 'UPDATE `users` SET `assurer` = 1 WHERE `users`.`id` = :user AND '.
143
144 'EXISTS(SELECT * FROM `cats_passed` AS `cp`, `cats_variant` AS `cv` '.
145 'WHERE `cp`.`variant_id` = `cv`.`id` AND `cv`.`type_id` = 1 AND '.
146 '`cp`.`user_id` = :user) AND '.
147
148 '(SELECT SUM(`points`) FROM `notary` WHERE `to` = :user AND '.
149 '`expire` < now()) >= 100';
150 $query_params['user'] = $this->id;
151 $this->db->query($query, $query_params);
152 }
153
154 /**
155 * @return boolean
156 */
157 public function getAssurerStatus() {
158 $query = 'SELECT 1 FROM `users` WHERE `users`.`id` = :user AND '.
159 '`assurer_blocked` = 0 AND '.
160
161 'EXISTS(SELECT * FROM `cats_passed` AS `cp`, `cats_variant` AS `cv` '.
162 'WHERE `cp`.`variant_id` = `cv`.`id` AND `cv`.`type_id` = 1 AND '.
163 '`cp`.`user_id` = :user) AND '.
164
165 '(SELECT SUM(`points`) FROM `notary` WHERE `to` = :user AND '.
166 '`expire` < now()) >= 100';
167 $query_params['user'] = $this->id;
168 $result = $this->db->query($query, $query_params);
169 if ($result->rowCount() === 1) {
170 return true;
171 }
172
173 return false;
174 }
175
176 /**
177 * @return Zend_Date
178 */
179 public function getDob() {
180 $query = 'select `dob` from `users` where `id` = :user';
181 $query_params['user'] = $this->id;
182 $row = $this->db->query($query, $query_params)->fetch();
183
184 return new Zend_Date($row['dob'], Zend_Date::ISO_8601);
185 }
186
187 /**
188 * @return int
189 */
190 public function getAge() {
191 $now = new Zend_Date();
192 $dob = $this->getDob();
193 $age = $now->get(Zend_Date::YEAR) - $dob->get(Zend_Date::YEAR);
194
195 // Did we have a happy birthday already this year?
196 $dob->setYear($now);
197 if ($dob->compare($now) > 0) {
198 $age -= 1;
199 }
200
201 return $age;
202 }
203
204 /**
205 * @return string
206 */
207 public function getPrimEmail() {
208 $query = 'select `email` from `users` where `id` = :user';
209 $query_params['user'] = $this->id;
210 $row = $this->db->query($query, $query_params)->fetch();
211
212 return $row['email'];
213 }
214
215 /**
216 * Assure another user. Usual restrictions apply
217 *
218 * @param $assuree Default_Model_User
219 * @param $points int
220 * @param $location string
221 * @param $date string
222 * @throws Exception
223 *
224 * @return int
225 * The amount of points that have been issued (might be less than
226 * $points)
227 */
228 public function assure(Default_Model_User $assuree, $points, $location, $date) {
229 // Sanitize inputs
230 $points = intval($points);
231 $location = stripslashes($location);
232 $date = stripslashes($date);
233
234 if (!$this->getAssurerStatus()) {
235 throw new Exception(
236 __METHOD__ . ': '.$this->id.' needs to be an assurer to do '.
237 'assurances');
238 }
239
240 if ($this->id === $assuree->id) {
241 throw new Exception(
242 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
243 'himself');
244 }
245
246 $query = 'select * from `notary` where `from`= :assurer and '.
247 '`to`= :assuree';
248 $query_params['assurer'] = $this->id;
249 $query_params['assuree'] = $assuree->id;
250 $result = $this->db->query($query, $query_params);
251 if ($result->rowCount() > 0 && $this->getPoints() < 200) {
252 throw new Exception(
253 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
254 $assuree->id .' more than once');
255 }
256
257 // Respect the maximum points
258 $max = $this->maxpoints();
259 $points = min($points, $max);
260
261 $rounddown = $points;
262 if ($max < 100) {
263 if ($assuree->getPoints() + $points > 100)
264 $rounddown = 100 - $assuree->getPoints();
265 } else {
266 if ($assuree->getPoints() + $points > $max)
267 $rounddown = $max - $assuree->getPoints();
268 }
269 if ($rounddown < 0) $rounddown = 0;
270
271 $query = 'select * from `notary` where `from` = :assurer and '.
272 '`to` = :assuree and `awarded` = :points and '.
273 '`location` = :location and `date` = :date';
274 $query_params['assurer'] = $this->id;
275 $query_params['assuree'] = $assuree->id;
276 $query_params['points'] = $points;
277 $query_params['location'] = $location;
278 $query_params['date'] = $date;
279 $result = $this->db->query($query, $query_params);
280 if ($result->rowCount() > 0) {
281 throw new Exception(
282 __METHOD__ . ': '.$this->id.' is not allowed to do the same '.
283 'assurance to '.$assuree->id.' more than once');
284 }
285
286 // Make sure it is empty
287 $assurance = array();
288 $assurance['from'] = $this->id;
289 $assurance['to'] = $assuree->id;
290 $assurance['points'] = $rounddown;
291 $assurance['awarded'] = $points;
292 $assurance['location'] = $location;
293 $assurance['date'] = $date;
294 $assurance['when'] = new Zend_Db_Expr('now()');
295
296 $this->db->insert('notary', $assurance);
297 $assuree->points += $rounddown;
298 $assuree->fixAssurerFlag();
299 return $rounddown;
300 }
301
302 /**
303 * Do an administrative increase
304 *
305 * @param $points int
306 * @param $location string
307 * @param $date string
308 */
309 public function adminIncrease($points, $location, $date) {
310 //Sanitize inputs
311 $points = intval($points);
312 $location = stripslashes($location);
313 $date = stripslashes($date);
314
315 $increase = array();
316 $increase['from'] = $this->id;
317 $increase['to'] = $this->id;
318 $increase['points'] = $points;
319 $increase['awarded'] = $points;
320 $increase['location'] = $location;
321 $increase['date'] = $date;
322 $increase['method'] = 'Administrative Increase';
323 $increase['when'] = new Zend_Db_Expr('now()');
324
325 $this->db->insert('notary', $increase);
326 $this->points += $points;
327
328 $this->fixAssurerFlag();
329 }
330
331 /**
332 * Maximum number of points the user may issue
333 *
334 * @return int
335 */
336 public function maxpoints() {
337 if (!$this->getAssurerStatus()) return 0;
338
339 if ($this->getAge() < 18) return 10;
340
341 $points = $this->getPoints();
342 if ($points >= 300) return 200;
343 if ($points >= 200) return 150;
344 if ($points >= 150) return 35;
345 if ($points >= 140) return 30;
346 if ($points >= 130) return 25;
347 if ($points >= 120) return 20;
348 if ($points >= 110) return 15;
349 if ($points >= 100) return 10;
350
351 // Should not get here
352 throw new Exception(
353 __METHOD__ . ': '.$this->id.' We have reached unreachable code');
354 }
355
356 /**
357 * Get the challenge types that are available in the database
358 *
359 * @param $db Zend_Db_Adapter_Abstract
360 * The database connection to use
361 *
362 * @return array(int => string)
363 */
364 public static function getAvailableChallengeTypes(
365 Zend_Db_Adapter_Abstract $db) {
366 $query = 'select `id`, `type_text` from `cats_type`';
367 return $db->fetchPairs($query);
368 }
369
370 /**
371 * Get the challenge variants for this type that are available in the
372 * database
373 *
374 * @param $db Zend_Db_Adapter_Abstract
375 * The database connection to use
376 * @param $type int
377 * The type of challenge you want to get the variants of
378 *
379 * @return array(int => string)
380 */
381 public static function getAvailableChallengeVariants(
382 Zend_Db_Adapter_Abstract $db, $type) {
383 $query = 'select `id`, `test_text` from `cats_variant`
384 where `type_id` = :type';
385 $query_params['type'] = $type;
386 return $db->fetchPairs($query, $query_params);
387 }
388
389 /**
390 * Assign the challenge to the user
391 *
392 * @param $type int
393 * The type of the challenge, has to be one of the keys returned by
394 * getAvailableChallengeTypes()
395 * @param $variant int
396 * The variant of the challenge, has to be one of the keys returned by
397 * getAvailableChallengeVariants()
398 * @param $date Zend_Date
399 * The date the challenge was passed, defaults to current time
400 */
401 public function assignChallenge($type, $variant, Zend_Date $date = null) {
402 $types = self::getAvailableChallengeTypes($this->db);
403 if (!isset($types[$type])) {
404 throw new Exception(
405 __METHOD__ . ': got wrong challenge type '.$type.' when '.
406 'assigning challenge to user '.$this->id);
407 }
408
409 $variants = self::getAvailableChallengeVariants($this->db, $type);
410 if (!isset($variants[$variant])) {
411 throw new Exception(
412 __METHOD__ . ': got wrong challenge variant '.$variant.' when '.
413 'assigning challenge to user '.$this->id);
414 }
415
416 $challenge = array();
417 $challenge['user_id'] = $this->id;
418 $challenge['variant_id'] = $variant;
419 if ($date !== null) {
420 $challenge['pass_date'] = $date->toString('Y-m-d H:i:s');
421 // otherwise default value of the database will be used
422 }
423
424 $this->db->insert('cats_passed', $challenge);
425
426 $this->fixAssurerFlag();
427 }
428
429 /**
430 * Get the flags that are set
431 *
432 * @return array (string => boolean)
433 */
434 public function getFlags() {
435 $flags = $this->db->select()->from('users', self::flags())
436 ->where('`id` = ?', $this->id)->query()->fetch();
437
438 foreach ($flags as $key => $value) {
439 if ($value === '0') {
440 $flags[$key] = false;
441 } else {
442 $flags[$key] = true;
443 }
444 }
445
446 return $flags;
447 }
448
449 /**
450 * Set the flags - to know which flags exist you might want to call
451 * getFlags() first
452 *
453 * @param $flags array (string => boolean)
454 * Currently unknown flags are silently ignored
455 */
456 public function setFlags(array $flags) {
457 $newflags = array();
458
459 // filter values
460 foreach (self::flags() as $flag) {
461 if (isset($flags[$flag])) {
462 if ($flags[$flag]) {
463 $newflags[$flag] = 1;
464 } else {
465 $newflags[$flag] = 0;
466 }
467 }
468 }
469
470 $where = $this->db->quoteInto('`id` = ?', $this->id, Zend_Db::INT_TYPE);
471 $this->db->update('users', $newflags, $where);
472 }
473
474 /**
475 * The flags from the `users` table that might be set
476 */
477 private static function flags() {
478 return array(
479 'verified',
480 'listme',
481 'codesign',
482 '1024bit',
483 'admin',
484 'orgadmin',
485 'ttpadmin',
486 'adadmin',
487 'board',
488 'tverify',
489 'locadmin',
490 'locked',
491 'assurer',
492 'assurer_blocked');
493 }
494 }