Moving to GitHub.
[platal.git] / include / validations.inc.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2014 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 } else {
444 $where = '';
445 }
446 $it = XDB::iterRow('SELECT data, DATE_FORMAT(stamp, "%Y%m%d%H%i%s")
447 FROM requests
448 ' . $where . '
449 ORDER BY stamp');
450 return PlIteratorUtils::map($it, 'toValidation');
451 }
452 }
453
454 /** Virtual class for profile related validation.
455 */
456 abstract class ProfileValidate extends Validate
457 {
458 // {{{ properties
459
460 public $profile;
461 public $profileOwner;
462 public $userIsProfileOwner;
463 public $ownerIsRegistered;
464
465 // }}}
466 // {{{ constructor
467
468 /** Constructor
469 * @param $_user: user object that required the validation.
470 * @param $_profile: profile object that is to be modified,
471 * its owner (if exists) can differ from $_user.
472 * @param $_unique: set to false if a profile can have multiple requests of this type.
473 * @param $_type: request's type.
474 */
475 public function __construct(User $_user, Profile $_profile, $_unique, $_type)
476 {
477 parent::__construct($_user, $_unique, $_type);
478 $this->profile = &$_profile;
479 $this->profileOwner = $this->profile->owner();
480 $this->userIsProfileOwner = (!is_null($this->profileOwner)
481 && $this->profileOwner->id() == $this->user->id());
482 $this->ownerIsRegistered = $this->profile->isActive();
483 }
484
485 // }}}
486 // {{{ function submit()
487
488 /** Sends data to validation.
489 * It also deletes multiple requests for a couple (profile, type)
490 * when $this->unique is set to true.
491 */
492 public function submit()
493 {
494 if ($this->unique) {
495 XDB::execute('DELETE FROM requests
496 WHERE pid = {?} AND type = {?}',
497 $this->profile->id(), $this->type);
498 }
499
500 $this->stamp = date('YmdHis');
501 XDB::execute('INSERT INTO requests (uid, pid, type, data, stamp)
502 VALUES ({?}, {?}, {?}, {?}, {?})',
503 $this->user->id(), $this->profile->id(), $this->type, $this, $this->stamp);
504
505 global $globals;
506 $globals->updateNbValid();
507 return true;
508 }
509
510 // }}}
511 // {{{ function update()
512
513 protected function update()
514 {
515 XDB::execute('UPDATE requests
516 SET data = {?}, stamp = stamp
517 WHERE pid = {?} AND type = {?} AND stamp = {?}',
518 $this, $this->profile->id(), $this->type, $this->stamp);
519 return true;
520 }
521
522 // }}}
523 // {{{ function clean()
524
525 /** Deletes request from 'requests' table.
526 * If $this->unique is set, it deletes every requests of this type.
527 */
528 public function clean()
529 {
530 global $globals;
531
532 if ($this->unique) {
533 $success = XDB::execute('DELETE FROM requests
534 WHERE pid = {?} AND type = {?}',
535 $this->profile->id(), $this->type);
536 } else {
537 $success = XDB::execute('DELETE FROM requests
538 WHERE pid = {?} AND type = {?} AND stamp = {?}',
539 $this->profile->id(), $this->type, $this->stamp);
540 }
541 $globals->updateNbValid();
542 return $success;
543 }
544
545 // }}}
546 // {{{ function sendmail
547
548 protected function sendmail($isok)
549 {
550 // Only sends email if the profile's owner exists and is registered.
551 if ($this->ownerIsRegistered) {
552 global $globals;
553
554 $mailer = new PlMailer();
555 $mailer->setSubject($this->_mail_subj());
556 $mailer->setFrom("validation+{$this->type}@{$globals->mail->domain}");
557 $mailer->addTo("\"{$this->profile->fullName()}\" <{$this->profileOwner->bestEmail()}>");
558 $mailer->addCc("validation+{$this->type}@{$globals->mail->domain}");
559 $body = ($this->profile->isFemale() ? "Chère camarade,\n\n" : "Cher camarade,\n\n")
560 . $this->_mail_body($isok)
561 . (Env::has('comm') ? "\n\n" . Env::v('comm') : '')
562 . "\n\nCordialement,\n-- \nL'équipe de Polytechnique.org\n"
563 . $this->_mail_ps($isok);
564 $mailer->setTxtBody(wordwrap($body));
565 $mailer->send();
566 }
567 }
568
569 // }}}
570 // {{{ function get_typed_request()
571
572 /**
573 * @param $pid: profile's pid
574 * @param $type: request's type
575 * @param $stamp: request's timestamp
576 *
577 * Should only be used to retrieve an object in the databse with Validate::get_typed_request(...)
578 */
579 static public function get_typed_request($pid, $type, $stamp = -1)
580 {
581 if ($stamp == -1) {
582 $res = XDB::query('SELECT data
583 FROM requests
584 WHERE pid = {?} and type = {?}',
585 $pid, $type);
586 } else {
587 $res = XDB::query('SELECT data, DATE_FORMAT(stamp, "%Y%m%d%H%i%s")
588 FROM requests
589 WHERE pid = {?} AND type = {?} and stamp = {?}',
590 $pid, $type, $stamp);
591 }
592 if ($result = $res->fetchOneCell()) {
593 $result = Validate::unserialize($result);
594 } else {
595 $result = false;
596 }
597 return $result;
598 }
599
600 // }}}
601 // {{{ function get_request_by_id()
602
603 static public function get_request_by_id($id)
604 {
605 list($pid, $type, $stamp) = explode('_', $id, 3);
606 return Validate::get_typed_request($pid, $type, $stamp);
607 }
608
609 // }}}
610 // {{{ function get_typed_requests()
611
612 /** Same as get_typed_request() but return an array of objects.
613 */
614 static public function get_typed_requests($pid, $type)
615 {
616 $res = XDB::iterRow('SELECT data
617 FROM requests
618 WHERE pid = {?} and type = {?}
619 ORDER BY stamp',
620 $pid, $type);
621 $array = array();
622 while (list($data) = $res->next()) {
623 $array[] = Validate::unserialize($data);
624 }
625 return $array;
626 }
627
628 // }}}
629 // {{{ function get_all_typed_requests()
630
631 /** Same as get_typed_request() but return an array of objects.
632 */
633 static public function get_all_typed_requests($type)
634 {
635 $res = XDB::iterRow('SELECT data
636 FROM requests
637 WHERE type = {?}
638 ORDER BY stamp',
639 $type);
640 $array = array();
641 while (list($data) = $res->next()) {
642 $array[] = Validate::unserialize($data);
643 }
644 return $array;
645 }
646
647 // }}}
648 // {{{ function get_typed_requests_count()
649
650 /** Same as get_typed_requests() but returns the count of available requests.
651 */
652 static public function get_typed_requests_count($pid, $type)
653 {
654 $res = XDB::query('SELECT COUNT(data)
655 FROM requests
656 WHERE pid = {?} and type = {?}',
657 $pid, $type);
658 return $res->fetchOneCell();
659 }
660
661 // }}}
662 // {{{ function id()
663
664 public function id()
665 {
666 return $this->profile->id() . '_' . $this->type . '_' . $this->stamp;
667 }
668
669 // }}}
670 }
671
672 foreach (glob(dirname(__FILE__) . '/validations/*.inc.php') as $file) {
673 require_once $file;
674 }
675
676 /* vim:set expandtab shiftwidth=4 tabstop=4 softtabstop=4 foldmethod=marker fenc=utf-8: */
677 ?>