390e81bb55dcb9ecba7061eaf526473f17a18e14
[platal.git] / include / validations.inc.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2013 Polytechnique.org *
4 * http://opensource.polytechnique.org/ *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program; if not, write to the Free Software *
18 * Foundation, Inc., *
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *
20 ***************************************************************************/
21
22 define('SIZE_MAX', 32768);
23
24 global $globals;
25
26
27 /** Virtual class to adapt for every possible implementation.
28 */
29 abstract class Validate
30 {
31 // {{{ properties
32
33 public $user;
34 public $formal;
35
36 public $stamp;
37 public $unique;
38 // Enable the refuse button.
39 public $refuse = true;
40
41 public $type;
42 public $comments = Array();
43 // Validations rules: comments for administrators.
44 public $rules = 'Mieux vaut laisser une demande de validation à un autre administrateur que de valider une requête illégale ou que de refuser une demande légitime.';
45
46 // Unless differently stated, a validation must be done by a site administrator.
47 public $requireAdmin = true;
48
49 // }}}
50 // {{{ constructor
51
52 /** Constructor
53 * @param $_user: user object that required the validation.
54 * @param $_unique: set to false if a profile can have multiple requests of this type.
55 * @param $_type: request's type.
56 */
57 public function __construct(User $_user, $_unique, $_type)
58 {
59 $this->user = &$_user;
60 $this->formal = !$this->user->hasProfile();
61 $this->stamp = date('YmdHis');
62 $this->unique = $_unique;
63 $this->type = $_type;
64 $this->promo = $this->user->promo();
65 }
66
67 // }}}
68 // {{{ function submit()
69
70 /** Sends data to validation.
71 * It also deletes multiple requests for a couple (profile, type)
72 * when $this->unique is set to true.
73 */
74 public function submit()
75 {
76 if ($this->unique) {
77 XDB::execute('DELETE FROM requests
78 WHERE uid = {?} AND type = {?}',
79 $this->user->id(), $this->type);
80 }
81
82 $this->stamp = date('YmdHis');
83 XDB::execute('INSERT INTO requests (uid, type, data, stamp)
84 VALUES ({?}, {?}, {?}, {?})',
85 $this->user->id(), $this->type, $this, $this->stamp);
86
87 global $globals;
88 $globals->updateNbValid();
89 return true;
90 }
91
92 // }}}
93 // {{{ function update()
94
95 protected function update()
96 {
97 XDB::execute('UPDATE requests
98 SET data = {?}, stamp = stamp
99 WHERE uid = {?} AND type = {?} AND stamp = {?}',
100 $this, $this->user->id(), $this->type, $this->stamp);
101 return true;
102 }
103
104 // }}}
105 // {{{ function clean()
106
107 /** Deletes request from 'requests' table.
108 * If $this->unique is set, it deletes every requests of this type.
109 */
110 public function clean()
111 {
112 global $globals;
113
114 if ($this->unique) {
115 $success = XDB::execute('DELETE FROM requests
116 WHERE uid = {?} AND type = {?}',
117 $this->user->id(), $this->type);
118 } else {
119 $success = XDB::execute('DELETE FROM requests
120 WHERE uid = {?} AND type = {?} AND stamp = {?}',
121 $this->user->id(), $this->type, $this->stamp);
122 }
123 $globals->updateNbValid();
124 return $success;
125 }
126
127 // }}}
128 // {{{ function handle_formu()
129
130 /** Handles form validation.
131 */
132 public function handle_formu()
133 {
134 if ($this->requireAdmin && !S::admin()) {
135 $this->trigError('Vous n\'avez pas les permissions nécessaires pour valider cette demande.');
136 return false;
137 }
138
139 if (Env::has('delete')) {
140 $this->clean();
141 $this->trigSuccess('Requête supprimée.');
142 return true;
143 }
144
145 // Data updates.
146 if (Env::has('edit')) {
147 if ($this->handle_editor()) {
148 $this->update();
149 $this->trigSuccess('Requête mise à jour.');
150 return true;
151 }
152 return false;
153 }
154
155 // Comment addition.
156 if (Env::has('hold') && Env::has('comm')) {
157 $formid = Env::i('formid');
158 foreach ($this->comments as $comment) {
159 if ($comment[2] === $formid) {
160 return true;
161 }
162 }
163 if (!strlen(trim(Env::v('comm')))) {
164 return true;
165 }
166 $this->comments[] = array(S::user()->login(), Env::v('comm'), $formid);
167
168 // Sends email to our hotline.
169 global $globals;
170 $mailer = new PlMailer();
171 $mailer->setSubject("Commentaires de validation {$this->type}");
172 $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
173 $mailer->addTo($globals->core->admin_email);
174
175 $body = "Validation {$this->type} pour {$this->user->login()}\n\n"
176 . S::user()->login() . " a ajouté le commentaire :\n\n"
177 . Env::v('comm') . "\n\n"
178 . "cf la discussion sur : " . $globals->baseurl . "/admin/validate";
179
180 $mailer->setTxtBody(wordwrap($body));
181 $mailer->send();
182
183 $this->update();
184 $this->trigSuccess('Commentaire ajouté.');
185 return true;
186 }
187
188 if (Env::has('accept')) {
189 if ($this->commit()) {
190 $this->sendmail(true);
191 $this->clean();
192 $this->trigSuccess('Email de validation envoyé');
193 return true;
194 } else {
195 $this->trigError('Erreur lors de la validation');
196 return false;
197 }
198 }
199
200 if (Env::has('refuse')) {
201 if (Env::v('comm')) {
202 $this->sendmail(false);
203 $this->clean();
204 $this->trigSuccess('Email de refus envoyé.');
205 return true;
206 } else {
207 $this->trigError('Pas de motivation pour le refus&nbsp;!!!');
208 }
209 }
210
211 return false;
212 }
213
214 // }}}
215 // {{{ function sendmail
216
217 protected function sendmail($isok)
218 {
219 global $globals;
220 $mailer = new PlMailer();
221 $mailer->setSubject($this->_mail_subj());
222 $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
223 $mailer->addTo("\"{$this->user->fullName()}\" <{$this->user->bestEmail()}>");
224 $mailer->addCc("validation+{$this->type}@{$globals->mail->domain}");
225
226 // If the user has no profile, we should be more formal as if she has one.
227 if ($this->formal) {
228 $body = ($this->user->isFemale() ? 'Bonjour Madame' : 'Bonjour Monsieur');
229 } else {
230 $body = ($this->user->isFemale() ? 'Chère camarade' : 'Cher camarade');
231 }
232 $body .= ",\n\n" . $this->_mail_body($isok)
233 . (Env::has('comm') ? "\n\n" . Env::v('comm') : '')
234 . "\n\nCordialement,\n-- \nL'équipe de Polytechnique.org\n"
235 . $this->_mail_ps($isok);
236
237 $mailer->setTxtBody(wordwrap($body));
238 $mailer->send();
239 }
240
241 // }}}
242 // {{{ function trig()
243
244 protected function trigError($msg)
245 {
246 Platal::page()->trigError($msg);
247 }
248
249 protected function trigWarning($msg)
250 {
251 Platal::page()->trigWarning($msg);
252 }
253
254 protected function trigSuccess($msg)
255 {
256 Platal::page()->trigSuccess($msg);
257 }
258
259 // }}}
260 // {{{ function get_typed_request()
261
262 /**
263 * @param $pid: profile's pid
264 * @param $type: request's type
265 * @param $stamp: request's timestamp
266 *
267 * Should only be used to retrieve an object in the databse with Validate::get_typed_request(...)
268 */
269 static public function get_typed_request($uid, $type, $stamp = -1)
270 {
271 if ($stamp == -1) {
272 $res = XDB::query('SELECT data
273 FROM requests
274 WHERE uid = {?} and type = {?}',
275 $uid, $type);
276 } else {
277 $res = XDB::query('SELECT data, DATE_FORMAT(stamp, "%Y%m%d%H%i%s")
278 FROM requests
279 WHERE uid = {?} AND type = {?} and stamp = {?}',
280 $uid, $type, $stamp);
281 }
282 if ($result = $res->fetchOneCell()) {
283 $result = Validate::unserialize($result);
284 } else {
285 $result = false;
286 }
287 return($result);
288 }
289
290 // }}}
291 // {{{ function get_request_by_id()
292
293 static public function get_request_by_id($id)
294 {
295 list($uid, $type, $stamp) = explode('_', $id, 3);
296 return Validate::get_typed_request($uid, $type, $stamp);
297 }
298
299 // }}}
300 // {{{ function get_typed_requests()
301
302 /** Same as get_typed_request() but return an array of objects.
303 */
304 static public function get_typed_requests($uid, $type)
305 {
306 $res = XDB::iterRow('SELECT data
307 FROM requests
308 WHERE uid = {?} and type = {?}',
309 $uid, $type);
310 $array = array();
311 while (list($data) = $res->next()) {
312 $array[] = Validate::unserialize($data);
313 }
314 return $array;
315 }
316
317 // }}}
318 // {{{ function get_typed_requests_count()
319
320 /** Same as get_typed_requests() but return the count of available requests.
321 */
322 static public function get_typed_requests_count($uid, $type)
323 {
324 $res = XDB::query('SELECT COUNT(data)
325 FROM requests
326 WHERE uid = {?} and type = {?}',
327 $uid, $type);
328 return $res->fetchOneCell();
329 }
330
331 // }}}
332 // {{{ function _mail_body
333
334 abstract protected function _mail_body($isok);
335
336 // }}}
337 // {{{ function _mail_subj
338
339 abstract protected function _mail_subj();
340
341 // }}}
342 // {{{ function _mail_ps
343
344 protected function _mail_ps($isok)
345 {
346 return '';
347 }
348
349 // }}}
350 // {{{ function commit()
351
352 /** Inserts data in database.
353 */
354 abstract public function commit();
355
356 // }}}
357 // {{{ function formu()
358
359 /** Retunrs the name of the form's template. */
360 abstract public function formu();
361
362 // }}}
363 // {{{ function editor()
364
365 /** Returns the name of the edition form's template. */
366 public function editor()
367 {
368 return null;
369 }
370
371 // }}}
372 // {{{ function answers()
373
374 /** Automatic answers table for this type of validation. */
375 public function answers()
376 {
377 static $answers_table;
378 if (!isset($answers_table[$this->type])) {
379 $r = XDB::query('SELECT id, title, answer
380 FROM requests_answers
381 WHERE category = {?}',
382 $this->type);
383 $answers_table[$this->type] = $r->fetchAllAssoc();
384 }
385 return $answers_table[$this->type];
386 }
387
388 // }}}
389 // {{{ function id()
390
391 public function id()
392 {
393 return str_replace(" ", "_", $this->user->id() . '_' . $this->type . '_' . $this->stamp);
394 }
395
396 // }}}
397 // {{{ function ruleText()
398
399 public function ruleText()
400 {
401 return str_replace('\'', '\\\'', $this->rules);
402 }
403
404 // }}}
405 // {{{ function unserialize()
406
407 public static function unserialize($data)
408 {
409 return unserialize($data);
410 }
411
412 // }}}
413
414 /** Return an iterator over the validation concerning the given type
415 * and the given user.
416 *
417 * @param type The type of the validations to fetch, null mean "any type"
418 * @param applyTo A User or a Profile object the validation applies to.
419 */
420 public static function iterate($type = null, $applyTo = null)
421 {
422 function toValidation($elt)
423 {
424 list($result, $stamp) = $elt;
425 $result = Validate::unserialize($result);
426 $result->stamp = $stamp;
427 return $result;
428 }
429
430 $where = array();
431 if ($type) {
432 $where[] = XDB::format('type = {?}', $type);
433 }
434 if ($applyTo) {
435 if ($applyTo instanceof User) {
436 $where[] = XDB::format('uid = {?}', $applyTo->id());
437 } else if ($applyTo instanceof Profile) {
438 $where[] = XDB::format('pid = {?}', $applyTo->id());
439 }
440 }
441 if (!empty($where)) {
442 $where = 'WHERE ' . implode('AND', $where);
443 }
444 $it = XDB::iterRow('SELECT data, DATE_FORMAT(stamp, "%Y%m%d%H%i%s")
445 FROM requests
446 ' . $where . '
447 ORDER BY stamp');
448 return PlIteratorUtils::map($it, 'toValidation');
449 }
450 }
451
452 /** Virtual class for profile related validation.
453 */
454 abstract class ProfileValidate extends Validate
455 {
456 // {{{ properties
457
458 public $profile;
459 public $profileOwner;
460 public $userIsProfileOwner;
461 public $ownerIsRegistered;
462
463 // }}}
464 // {{{ constructor
465
466 /** Constructor
467 * @param $_user: user object that required the validation.
468 * @param $_profile: profile object that is to be modified,
469 * its owner (if exists) can differ from $_user.
470 * @param $_unique: set to false if a profile can have multiple requests of this type.
471 * @param $_type: request's type.
472 */
473 public function __construct(User $_user, Profile $_profile, $_unique, $_type)
474 {
475 parent::__construct($_user, $_unique, $_type);
476 $this->profile = &$_profile;
477 $this->profileOwner = $this->profile->owner();
478 $this->userIsProfileOwner = (!is_null($this->profileOwner)
479 && $this->profileOwner->id() == $this->user->id());
480 $this->ownerIsRegistered = $this->profile->isActive();
481 }
482
483 // }}}
484 // {{{ function submit()
485
486 /** Sends data to validation.
487 * It also deletes multiple requests for a couple (profile, type)
488 * when $this->unique is set to true.
489 */
490 public function submit()
491 {
492 if ($this->unique) {
493 XDB::execute('DELETE FROM requests
494 WHERE pid = {?} AND type = {?}',
495 $this->profile->id(), $this->type);
496 }
497
498 $this->stamp = date('YmdHis');
499 XDB::execute('INSERT INTO requests (uid, pid, type, data, stamp)
500 VALUES ({?}, {?}, {?}, {?}, {?})',
501 $this->user->id(), $this->profile->id(), $this->type, $this, $this->stamp);
502
503 global $globals;
504 $globals->updateNbValid();
505 return true;
506 }
507
508 // }}}
509 // {{{ function update()
510
511 protected function update()
512 {
513 XDB::execute('UPDATE requests
514 SET data = {?}, stamp = stamp
515 WHERE pid = {?} AND type = {?} AND stamp = {?}',
516 $this, $this->profile->id(), $this->type, $this->stamp);
517 return true;
518 }
519
520 // }}}
521 // {{{ function clean()
522
523 /** Deletes request from 'requests' table.
524 * If $this->unique is set, it deletes every requests of this type.
525 */
526 public function clean()
527 {
528 global $globals;
529
530 if ($this->unique) {
531 $success = XDB::execute('DELETE FROM requests
532 WHERE pid = {?} AND type = {?}',
533 $this->profile->id(), $this->type);
534 } else {
535 $success = XDB::execute('DELETE FROM requests
536 WHERE pid = {?} AND type = {?} AND stamp = {?}',
537 $this->profile->id(), $this->type, $this->stamp);
538 }
539 $globals->updateNbValid();
540 return $success;
541 }
542
543 // }}}
544 // {{{ function sendmail
545
546 protected function sendmail($isok)
547 {
548 // Only sends email if the profile's owner exists and is registered.
549 if ($this->ownerIsRegistered) {
550 global $globals;
551
552 $mailer = new PlMailer();
553 $mailer->setSubject($this->_mail_subj());
554 $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
555 $mailer->addTo("\"{$this->profile->fullName()}\" <{$this->profileOwner->bestEmail()}>");
556 $mailer->addCc("validation+{$this->type}@{$globals->mail->domain}");
557 $body = ($this->profile->isFemale() ? "Chère camarade,\n\n" : "Cher camarade,\n\n")
558 . $this->_mail_body($isok)
559 . (Env::has('comm') ? "\n\n" . Env::v('comm') : '')
560 . "\n\nCordialement,\n-- \nL'équipe de Polytechnique.org\n"
561 . $this->_mail_ps($isok);
562 $mailer->setTxtBody(wordwrap($body));
563 $mailer->send();
564 }
565 }
566
567 // }}}
568 // {{{ function get_typed_request()
569
570 /**
571 * @param $pid: profile's pid
572 * @param $type: request's type
573 * @param $stamp: request's timestamp
574 *
575 * Should only be used to retrieve an object in the databse with Validate::get_typed_request(...)
576 */
577 static public function get_typed_request($pid, $type, $stamp = -1)
578 {
579 if ($stamp == -1) {
580 $res = XDB::query('SELECT data
581 FROM requests
582 WHERE pid = {?} and type = {?}',
583 $pid, $type);
584 } else {
585 $res = XDB::query('SELECT data, DATE_FORMAT(stamp, "%Y%m%d%H%i%s")
586 FROM requests
587 WHERE pid = {?} AND type = {?} and stamp = {?}',
588 $pid, $type, $stamp);
589 }
590 if ($result = $res->fetchOneCell()) {
591 $result = Validate::unserialize($result);
592 } else {
593 $result = false;
594 }
595 return $result;
596 }
597
598 // }}}
599 // {{{ function get_request_by_id()
600
601 static public function get_request_by_id($id)
602 {
603 list($pid, $type, $stamp) = explode('_', $id, 3);
604 return Validate::get_typed_request($pid, $type, $stamp);
605 }
606
607 // }}}
608 // {{{ function get_typed_requests()
609
610 /** Same as get_typed_request() but return an array of objects.
611 */
612 static public function get_typed_requests($pid, $type)
613 {
614 $res = XDB::iterRow('SELECT data
615 FROM requests
616 WHERE pid = {?} and type = {?}
617 ORDER BY stamp',
618 $pid, $type);
619 $array = array();
620 while (list($data) = $res->next()) {
621 $array[] = Validate::unserialize($data);
622 }
623 return $array;
624 }
625
626 // }}}
627 // {{{ function get_all_typed_requests()
628
629 /** Same as get_typed_request() but return an array of objects.
630 */
631 static public function get_all_typed_requests($type)
632 {
633 $res = XDB::iterRow('SELECT data
634 FROM requests
635 WHERE type = {?}
636 ORDER BY stamp',
637 $type);
638 $array = array();
639 while (list($data) = $res->next()) {
640 $array[] = Validate::unserialize($data);
641 }
642 return $array;
643 }
644
645 // }}}
646 // {{{ function get_typed_requests_count()
647
648 /** Same as get_typed_requests() but returns the count of available requests.
649 */
650 static public function get_typed_requests_count($pid, $type)
651 {
652 $res = XDB::query('SELECT COUNT(data)
653 FROM requests
654 WHERE pid = {?} and type = {?}',
655 $pid, $type);
656 return $res->fetchOneCell();
657 }
658
659 // }}}
660 // {{{ function id()
661
662 public function id()
663 {
664 return $this->profile->id() . '_' . $this->type . '_' . $this->stamp;
665 }
666
667 // }}}
668 }
669
670 foreach (glob(dirname(__FILE__) . '/validations/*.inc.php') as $file) {
671 require_once $file;
672 }
673
674 /* vim: set expandtab shiftwidth=4 tabstop=4 softtabstop=4 foldmethod=marker enc=utf-8: */
675 ?>