Merge branch 'bug-932'
[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 if($this->points < 100) return;
120
121 $this->points = 100;
122
123 $query = "SELECT COUNT(`points`) AS `total` FROM `notary` " .
124 "WHERE `from` = :user AND `method` = 'Face to Face Meeting' AND `from` != `to`";
125 $query_params['user'] = $this->id;
126 $row = $this->db->query($query, $query_params)->fetch();
127 if ($row['total'] === null) $row['total'] = 0;
128
129 $this->points += ($row['total'] > 25) ? 50 : 2 * $row['total'];
130 }
131
132 /**
133 * Get points of the user
134 *
135 * @return int
136 * The amount of points the user has
137 */
138 public function getPoints()
139 {
140 if ($this->points === null) {
141 $this->refreshPoints();
142 }
143
144 return $this->points;
145 }
146
147 /**
148 * Fix the assurer flag for the user
149 */
150 public function fixAssurerFlag()
151 {
152 // TODO: unset flag if requirements are not met
153
154 $query = 'UPDATE `users` SET `assurer` = 1 WHERE `users`.`id` = :user AND '.
155
156 'EXISTS(SELECT * FROM `cats_passed` AS `cp`, `cats_variant` AS `cv` '.
157 'WHERE `cp`.`variant_id` = `cv`.`id` AND `cv`.`type_id` = 1 AND '.
158 '`cp`.`user_id` = :user) AND '.
159
160 '(SELECT SUM(`points`) FROM `notary` WHERE `to` = :user AND '.
161 '`expire` < now()) >= 100';
162 $query_params['user'] = $this->id;
163 $this->db->query($query, $query_params);
164
165 $this->refreshPoints();
166 }
167
168 /**
169 * @return boolean
170 */
171 public function getAssurerStatus() {
172 $this->refreshPoints();
173
174 $query = 'SELECT 1 FROM `users` WHERE `users`.`id` = :user AND '.
175 '`assurer_blocked` = 0 AND '.
176
177 'EXISTS(SELECT * FROM `cats_passed` AS `cp`, `cats_variant` AS `cv` '.
178 'WHERE `cp`.`variant_id` = `cv`.`id` AND `cv`.`type_id` = 1 AND '.
179 '`cp`.`user_id` = :user) AND '.
180
181 '(SELECT SUM(`points`) FROM `notary` WHERE `to` = :user AND '.
182 '`expire` < now()) >= 100';
183 $query_params['user'] = $this->id;
184 $result = $this->db->query($query, $query_params);
185 if ($result->rowCount() === 1) {
186 return true;
187 }
188
189 return false;
190 }
191
192 /**
193 * @return Zend_Date
194 */
195 public function getDob() {
196 $query = 'select `dob` from `users` where `id` = :user';
197 $query_params['user'] = $this->id;
198 $row = $this->db->query($query, $query_params)->fetch();
199
200 return new Zend_Date($row['dob'], Zend_Date::ISO_8601);
201 }
202
203 /**
204 * @return int
205 */
206 public function getAge() {
207 $now = new Zend_Date();
208 $dob = $this->getDob();
209 $age = $now->get(Zend_Date::YEAR) - $dob->get(Zend_Date::YEAR);
210
211 // Did we have a happy birthday already this year?
212 $dob->setYear($now);
213 if ($dob->compare($now) > 0) {
214 $age -= 1;
215 }
216
217 return $age;
218 }
219
220 /**
221 * @return string
222 */
223 public function getPrimEmail() {
224 $query = 'select `email` from `users` where `id` = :user';
225 $query_params['user'] = $this->id;
226 $row = $this->db->query($query, $query_params)->fetch();
227
228 return $row['email'];
229 }
230
231 /**
232 * Assure another user. Usual restrictions apply
233 *
234 * @param $assuree Default_Model_User
235 * @param $points int
236 * @param $location string
237 * @param $date string
238 * @throws Exception
239 *
240 * @return int
241 * The amount of points that have been issued (might be less than
242 * $points)
243 */
244 public function assure(Default_Model_User $assuree, $points, $location, $date) {
245 // Sanitize inputs
246 $points = intval($points);
247 $location = stripslashes($location);
248 $date = stripslashes($date);
249
250 if (!$this->getAssurerStatus()) {
251 throw new Exception(
252 __METHOD__ . ': '.$this->id.' needs to be an assurer to do '.
253 'assurances');
254 }
255
256 if ($this->id === $assuree->id) {
257 throw new Exception(
258 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
259 'himself');
260 }
261
262 $query = 'select * from `notary` where `from`= :assurer and '.
263 '`to`= :assuree';
264 $query_params['assurer'] = $this->id;
265 $query_params['assuree'] = $assuree->id;
266 $result = $this->db->query($query, $query_params);
267 if ($result->rowCount() > 0 && $this->getPoints() < 200) {
268 throw new Exception(
269 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
270 $assuree->id .' more than once');
271 }
272
273 // Respect the maximum points
274 $max = $this->maxpoints();
275 $points = min($points, $max);
276
277 $rounddown = $points;
278 if ($max < 100) {
279 if ($assuree->getPoints() + $points > 100)
280 $rounddown = 100 - $assuree->getPoints();
281 } else {
282 if ($assuree->getPoints() + $points > $max)
283 $rounddown = $max - $assuree->getPoints();
284 }
285 if ($rounddown < 0) $rounddown = 0;
286
287 $query = 'select * from `notary` where `from` = :assurer and '.
288 '`to` = :assuree and `awarded` = :points and '.
289 '`location` = :location and `date` = :date';
290 $query_params['assurer'] = $this->id;
291 $query_params['assuree'] = $assuree->id;
292 $query_params['points'] = $points;
293 $query_params['location'] = $location;
294 $query_params['date'] = $date;
295 $result = $this->db->query($query, $query_params);
296 if ($result->rowCount() > 0) {
297 throw new Exception(
298 __METHOD__ . ': '.$this->id.' is not allowed to do the same '.
299 'assurance to '.$assuree->id.' more than once');
300 }
301
302 // Make sure it is empty
303 $assurance = array();
304 $assurance['from'] = $this->id;
305 $assurance['to'] = $assuree->id;
306 $assurance['points'] = $rounddown;
307 $assurance['awarded'] = $points;
308 $assurance['location'] = $location;
309 $assurance['date'] = $date;
310 $assurance['when'] = new Zend_Db_Expr('now()');
311
312 $this->db->insert('notary', $assurance);
313 $assuree->points += $rounddown;
314 $assuree->fixAssurerFlag();
315 return $rounddown;
316 }
317
318 /**
319 * Do an administrative increase
320 *
321 * @param $points int
322 * @param $location string
323 * @param $date string
324 */
325 public function adminIncrease($points, $location, $date) {
326 //Sanitize inputs
327 $points = intval($points);
328 $location = stripslashes($location);
329 $date = stripslashes($date);
330
331 $increase = array();
332 $increase['from'] = $this->id;
333 $increase['to'] = $this->id;
334 $increase['points'] = $points;
335 $increase['awarded'] = $points;
336 $increase['location'] = $location;
337 $increase['date'] = $date;
338 $increase['method'] = 'Administrative Increase';
339 $increase['when'] = new Zend_Db_Expr('now()');
340
341 $this->db->insert('notary', $increase);
342 $this->points += $points;
343
344 $this->fixAssurerFlag();
345 }
346
347 /**
348 * Maximum number of points the user may issue
349 *
350 * @return int
351 */
352 public function maxpoints() {
353 if (!$this->getAssurerStatus()) return 0;
354
355 if ($this->getAge() < 18) return 10;
356
357 $points = $this->getPoints();
358 if ($points >= 300) return 200;
359 if ($points >= 200) return 150;
360 if ($points >= 150) return 35;
361 if ($points >= 140) return 30;
362 if ($points >= 130) return 25;
363 if ($points >= 120) return 20;
364 if ($points >= 110) return 15;
365 if ($points >= 100) return 10;
366
367 // Should not get here
368 throw new Exception(
369 __METHOD__ . ': '.$this->id.' We have reached unreachable code');
370 }
371
372 /**
373 * Get the challenge types that are available in the database
374 *
375 * @param $db Zend_Db_Adapter_Abstract
376 * The database connection to use
377 *
378 * @return array(int => string)
379 */
380 public static function getAvailableChallengeTypes(
381 Zend_Db_Adapter_Abstract $db) {
382 $query = 'select `id`, `type_text` from `cats_type`';
383 return $db->fetchPairs($query);
384 }
385
386 /**
387 * Get the challenge variants for this type that are available in the
388 * database
389 *
390 * @param $db Zend_Db_Adapter_Abstract
391 * The database connection to use
392 * @param $type int
393 * The type of challenge you want to get the variants of
394 *
395 * @return array(int => string)
396 */
397 public static function getAvailableChallengeVariants(
398 Zend_Db_Adapter_Abstract $db, $type) {
399 $query = 'select `id`, `test_text` from `cats_variant`
400 where `type_id` = :type';
401 $query_params['type'] = $type;
402 return $db->fetchPairs($query, $query_params);
403 }
404
405 /**
406 * Assign the challenge to the user
407 *
408 * @param $type int
409 * The type of the challenge, has to be one of the keys returned by
410 * getAvailableChallengeTypes()
411 * @param $variant int
412 * The variant of the challenge, has to be one of the keys returned by
413 * getAvailableChallengeVariants()
414 * @param $date Zend_Date
415 * The date the challenge was passed, defaults to current time
416 */
417 public function assignChallenge($type, $variant, Zend_Date $date = null) {
418 $types = self::getAvailableChallengeTypes($this->db);
419 if (!isset($types[$type])) {
420 throw new Exception(
421 __METHOD__ . ': got wrong challenge type '.$type.' when '.
422 'assigning challenge to user '.$this->id);
423 }
424
425 $variants = self::getAvailableChallengeVariants($this->db, $type);
426 if (!isset($variants[$variant])) {
427 throw new Exception(
428 __METHOD__ . ': got wrong challenge variant '.$variant.' when '.
429 'assigning challenge to user '.$this->id);
430 }
431
432 $challenge = array();
433 $challenge['user_id'] = $this->id;
434 $challenge['variant_id'] = $variant;
435 if ($date !== null) {
436 $challenge['pass_date'] = $date->toString('Y-m-d H:i:s');
437 // otherwise default value of the database will be used
438 }
439
440 $this->db->insert('cats_passed', $challenge);
441
442 $this->fixAssurerFlag();
443 }
444
445 /**
446 * Get the flags that are set
447 *
448 * @return array (string => boolean)
449 */
450 public function getFlags() {
451 $flags = $this->db->select()->from('users', self::flags())
452 ->where('`id` = ?', $this->id)->query()->fetch();
453
454 foreach ($flags as $key => $value) {
455 if ($value === '0') {
456 $flags[$key] = false;
457 } else {
458 $flags[$key] = true;
459 }
460 }
461
462 return $flags;
463 }
464
465 /**
466 * Set the flags - to know which flags exist you might want to call
467 * getFlags() first
468 *
469 * @param $flags array (string => boolean)
470 * Currently unknown flags are silently ignored
471 */
472 public function setFlags(array $flags) {
473 $newflags = array();
474
475 // filter values
476 foreach (self::flags() as $flag) {
477 if (isset($flags[$flag])) {
478 if ($flags[$flag]) {
479 $newflags[$flag] = 1;
480 } else {
481 $newflags[$flag] = 0;
482 }
483 }
484 }
485
486 $where = $this->db->quoteInto('`id` = ?', $this->id, Zend_Db::INT_TYPE);
487 $this->db->update('users', $newflags, $where);
488 }
489
490 /**
491 * The flags from the `users` table that might be set
492 */
493 private static function flags() {
494 return array(
495 'verified',
496 'listme',
497 'codesign',
498 '1024bit',
499 'admin',
500 'orgadmin',
501 'ttpadmin',
502 'adadmin',
503 'board',
504 'tverify',
505 'locadmin',
506 'locked',
507 'assurer',
508 'assurer_blocked');
509 }
510 }