bug 1391: removed insert of administrative increase entry for normal assurance
[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';
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,
229 $date) {
230 // Sanitize inputs
231 $points = intval($points);
232 $location = stripslashes($location);
233 $date = stripslashes($date);
234
235 if (!$this->getAssurerStatus()) {
236 throw new Exception(
237 __METHOD__ . ': '.$this->id.' needs to be an assurer to do '.
238 'assurances');
239 }
240
241 if ($this->id === $assuree->id) {
242 throw new Exception(
243 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
244 'himself');
245 }
246
247 $query = 'select * from `notary` where `from`= :assurer and '.
248 '`to`= :assuree';
249 $query_params['assurer'] = $this->id;
250 $query_params['assuree'] = $assuree->id;
251 $result = $this->db->query($query, $query_params);
252 if ($result->rowCount() > 0 && $this->getPoints() < 200) {
253 throw new Exception(
254 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
255 $assuree->id .' more than once');
256 }
257
258 // Respect the maximum points
259 $max = $this->maxpoints();
260 $points = min($points, $max);
261
262 $rounddown = $points;
263 if ($max < 100) {
264 if ($assuree->getPoints() + $points > 100)
265 $rounddown = 100 - $assuree->getPoints();
266 } else {
267 if ($assuree->getPoints() + $points > $max)
268 $rounddown = $max - $assuree->getPoints();
269 }
270 if ($rounddown < 0) $rounddown = 0;
271
272 $query = 'select * from `notary` where `from` = :assurer and '.
273 '`to` = :assuree and `awarded` = :points and '.
274 '`location` = :location and `date` = :date';
275 $query_params['assurer'] = $this->id;
276 $query_params['assuree'] = $assuree->id;
277 $query_params['points'] = $points;
278 $query_params['location'] = $location;
279 $query_params['date'] = $date;
280 $result = $this->db->query($query, $query_params);
281 if ($result->rowCount() > 0) {
282 throw new Exception(
283 __METHOD__ . ': '.$this->id.' is not allowed to do the same '.
284 'assurance to '.$assuree->id.' more than once');
285 }
286
287 // Make sure it is empty
288 $assurance = array();
289 $assurance['from'] = $this->id;
290 $assurance['to'] = $assuree->id;
291 $assurance['points'] = $rounddown;
292 $assurance['awarded'] = $points;
293 $assurance['location'] = $location;
294 $assurance['date'] = $date;
295 $assurance['when'] = new Zend_Db_Expr('now()');
296
297 $this->db->insert('notary', $assurance);
298 $assuree->points += $rounddown;
299 $assuree->fixAssurerFlag();
300 return $rounddown;
301 }
302
303 /**
304 * Do an administrative increase
305 *
306 * @param $points int
307 * @param $location string
308 * @param $date string
309 */
310 public function adminIncrease($points, $location, $date) {
311 //Sanitize inputs
312 $points = intval($points);
313 $location = stripslashes($location);
314 $date = stripslashes($date);
315
316 $increase = array();
317 $increase['from'] = $this->id;
318 $increase['to'] = $this->id;
319 $increase['points'] = $points;
320 $increase['awarded'] = $points;
321 $increase['location'] = $location;
322 $increase['date'] = $date;
323 $increase['method'] = 'Administrative Increase';
324 $increase['when'] = new Zend_Db_Expr('now()');
325
326 $this->db->insert('notary', $increase);
327 $this->points += $points;
328
329 $this->fixAssurerFlag();
330 }
331
332 /**
333 * Maximum number of points the user may issue
334 *
335 * @return int
336 */
337 public function maxpoints() {
338 if (!$this->getAssurerStatus()) return 0;
339
340 if ($this->getAge() < 18) return 10;
341
342 $points = $this->getPoints();
343 if ($points >= 300) return 200;
344 if ($points >= 200) return 150;
345 if ($points >= 150) return 35;
346 if ($points >= 140) return 30;
347 if ($points >= 130) return 25;
348 if ($points >= 120) return 20;
349 if ($points >= 110) return 15;
350 if ($points >= 100) return 10;
351
352 // Should not get here
353 throw new Exception(
354 __METHOD__ . ': '.$this->id.' We have reached unreachable code');
355 }
356
357 /**
358 * Get the challenge types that are available in the database
359 *
360 * @param $db Zend_Db_Adapter_Abstract
361 * The database connection to use
362 *
363 * @return array(int => string)
364 */
365 public static function getAvailableChallengeTypes(
366 Zend_Db_Adapter_Abstract $db) {
367 $query = 'select `id`, `type_text` from `cats_type`';
368 return $db->fetchPairs($query);
369 }
370
371 /**
372 * Get the challenge variants for this type that are available in the
373 * database
374 *
375 * @param $db Zend_Db_Adapter_Abstract
376 * The database connection to use
377 * @param $type int
378 * The type of challenge you want to get the variants of
379 *
380 * @return array(int => string)
381 */
382 public static function getAvailableChallengeVariants(
383 Zend_Db_Adapter_Abstract $db, $type) {
384 $query = 'select `id`, `test_text` from `cats_variant`
385 where `type_id` = :type';
386 $query_params['type'] = $type;
387 return $db->fetchPairs($query, $query_params);
388 }
389
390 /**
391 * Assign the challenge to the user
392 *
393 * @param $type int
394 * The type of the challenge, has to be one of the keys returned by
395 * getAvailableChallengeTypes()
396 * @param $variant int
397 * The variant of the challenge, has to be one of the keys returned by
398 * getAvailableChallengeVariants()
399 * @param $date Zend_Date
400 * The date the challenge was passed, defaults to current time
401 */
402 public function assignChallenge($type, $variant, Zend_Date $date = null) {
403 $types = self::getAvailableChallengeTypes($this->db);
404 if (!isset($types[$type])) {
405 throw new Exception(
406 __METHOD__ . ': got wrong challenge type '.$type.' when '.
407 'assigning challenge to user '.$this->id);
408 }
409
410 $variants = self::getAvailableChallengeVariants($this->db, $type);
411 if (!isset($variants[$variant])) {
412 throw new Exception(
413 __METHOD__ . ': got wrong challenge variant '.$variant.' when '.
414 'assigning challenge to user '.$this->id);
415 }
416
417 $challenge = array();
418 $challenge['user_id'] = $this->id;
419 $challenge['variant_id'] = $variant;
420 if ($date !== null) {
421 $challenge['pass_date'] = $date->toString('Y-m-d H:i:s');
422 // otherwise default value of the database will be used
423 }
424
425 $this->db->insert('cats_passed', $challenge);
426
427 $this->fixAssurerFlag();
428 }
429
430 /**
431 * Get the flags that are set
432 *
433 * @return array (string => boolean)
434 */
435 public function getFlags() {
436 $flags = $this->db->select()->from('users', self::flags())
437 ->where('`id` = ?', $this->id)->query()->fetch();
438
439 foreach ($flags as $key => $value) {
440 if ($value === '0') {
441 $flags[$key] = false;
442 } else {
443 $flags[$key] = true;
444 }
445 }
446
447 return $flags;
448 }
449
450 /**
451 * Set the flags - to know which flags exist you might want to call
452 * getFlags() first
453 *
454 * @param $flags array (string => boolean)
455 * Currently unknown flags are silently ignored
456 */
457 public function setFlags(array $flags) {
458 $newflags = array();
459
460 // filter values
461 foreach (self::flags() as $flag) {
462 if (isset($flags[$flag])) {
463 if ($flags[$flag]) {
464 $newflags[$flag] = 1;
465 } else {
466 $newflags[$flag] = 0;
467 }
468 }
469 }
470
471 $where = $this->db->quoteInto('`id` = ?', $this->id, Zend_Db::INT_TYPE);
472 $this->db->update('users', $newflags, $where);
473 }
474
475 /**
476 * The flags from the `users` table that might be set
477 */
478 private static function flags() {
479 return array(
480 'verified',
481 'listme',
482 'codesign',
483 '1024bit',
484 'admin',
485 'orgadmin',
486 'ttpadmin',
487 'adadmin',
488 'board',
489 'tverify',
490 'locadmin',
491 'locked',
492 'assurer',
493 'assurer_blocked');
494 }
495 }