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