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