Factor out functionality into a model so the controllers get more lightweight
[cacert-mgr.git] / manager / application / models / User.php
1 <?php
2 /**
3 * @author Michael Tänzer
4 */
5
6 class Application_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 Application_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 Application_Model_User($db, $row['id']);
44 }
45
46 /**
47 * Get an user object for the currently logged in user
48 *
49 * @return Application_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 Application_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 Application_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 return $now->sub($this->getDob())->toValue(Zend_Date::YEAR);
172 }
173
174 /**
175 * Assure another user. Usual restrictions apply
176 *
177 * @param $assuree Application_Model_User
178 * @param $points int
179 * @param $location string
180 * @param $date string
181 * @throws Exception
182 *
183 * @return int
184 * The amount of points that have been issued (might be less than
185 * $points)
186 */
187 public function assure(Application_Model_User $assuree, $points, $location,
188 $date) {
189 // Sanitize inputs
190 $points = intval($points);
191 $location = stripslashes($location);
192 $date = stripslashes($date);
193
194 if (!$this->getAssurerStatus()) {
195 throw new Exception(
196 __METHOD__ . ': '.$this->id.' needs to be an assurer to do '.
197 'assurances');
198 }
199
200 if ($this->id === $assuree->id) {
201 throw new Exception(
202 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
203 'himself');
204 }
205
206 $query = 'select * from `notary` where `from`= :assurer and '.
207 '`to`= :assuree';
208 $query_params['assurer'] = $this->id;
209 $query_params['assuree'] = $assuree->id;
210 $result = $this->db->query($query, $query_params);
211 if ($result->rowCount() > 0 && $this->getPoints() < 200) {
212 throw new Exception(
213 __METHOD__ . ': '.$this->id.' is not allowed to assure '.
214 $assuree->id .' more than once');
215 }
216
217 // Respect the maximum points
218 $max = $this->maxpoints();
219 $points = min($points, $max);
220
221 $rounddown = $points;
222 if ($max < 100) {
223 if ($assuree->getPoints() + $points > 100)
224 $rounddown = 100 - $assuree->getPoints();
225 } else {
226 if ($assuree->getPoints() + $points > $max)
227 $rounddown = $max - $assuree->getPoints();
228 }
229 if ($rounddown < 0) $rounddown = 0;
230
231 $query = 'select * from `notary` where `from` = :assurer and '.
232 '`to` = :assuree and `awarded` = :points and '.
233 '`location` = :location and `date` = :date';
234 $query_params['assurer'] = $this->id;
235 $query_params['assuree'] = $assuree->id;
236 $query_params['points'] = $points;
237 $query_params['location'] = $location;
238 $query_params['date'] = $date;
239 $result = $this->db->query($query, $query_params);
240 if ($result->rowCount() > 0) {
241 throw new Exception(
242 __METHOD__ . ': '.$this->id.' is not allowed to do the same '.
243 'assurance to '.$assuree->id.' more than once');
244 }
245
246 // Make sure it is empty
247 $assurance = array();
248 $assurance['from'] = $this->id;
249 $assurance['to'] = $assuree->id;
250 $assurance['points'] = $rounddown;
251 $assurance['awarded'] = $points;
252 $assurance['location'] = $location;
253 $assurance['date'] = $date;
254 $assurance['when'] = new Zend_Db_Expr('now()');
255
256 $this->db->insert('notary', $assurance);
257 $assuree->points += $rounddown;
258 $assuree->fixAssurerFlag();
259
260 if ($this->getPoints() < 150) {
261 $addpoints = 0;
262 if ($this->getPoints() < 149 && $this->getPoints() >= 100) {
263 $addpoints = 2;
264 } elseif ($this->getPoints() === 149) {
265 $addpoints = 1;
266 }
267
268 $increase = array();
269 $increase['from'] = $this->id;
270 $increase['to'] = $this->id;
271 $increase['points'] = $addpoints;
272 $increase['awarded'] = $addpoints;
273 $increase['location'] = $location;
274 $increase['date'] = $date;
275 $increase['method'] = 'Administrative Increase';
276 $increase['when'] = new Zend_Db_Expr('now()');
277
278 $this->db->insert('notary', $increase);
279 $this->points += $addpoints;
280 // No need to fix assurer flag here
281 }
282
283 return $rounddown;
284 }
285
286 /**
287 * Maximum number of points the user may issue
288 *
289 * @return int
290 */
291 private function maxpoints() {
292 if (!$this->getAssurerStatus()) return 0;
293
294 if ($this->getAge() < 18) return 10;
295
296 $points = $this->getPoints();
297 if ($points >= 300) return 200;
298 if ($points >= 200) return 150;
299 if ($points >= 150) return 35;
300 if ($points >= 140) return 30;
301 if ($points >= 130) return 25;
302 if ($points >= 120) return 20;
303 if ($points >= 110) return 15;
304 if ($points >= 100) return 10;
305
306 // Should not get here
307 throw new Exception(
308 __METHOD__ . ': '.$this->id.' We have reached unreachable code');
309 }
310 }