Survey module : core components, basic editing and administration
[platal.git] / modules / survey / survey.inc.php
CommitLineData
8fe81c50 1<?php
2/***************************************************************************
3 * Copyright (C) 2003-2007 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// {{{ class Survey : static database managing functions
23class SurveyDB
24{
25 // {{{ static function retrieveList() : gets the list of available survey (current, old and not validated surveys)
26 public static function retrieveList($type, $tpl = true)
27 {
28 switch ($type) {
29 case 'w':
30 case 'waiting' :
31 $where = 'valid=0';
32 break;
33 case 'c':
34 case 'current':
35 $where = 'valid=1 AND end > NOW()';
36 break;
37 case 'o':
38 case 'old':
39 $where = 'valid=1 AND end <= NOW()';
40 break;
41 default:
42 return null;
43 }
44 $sql = 'SELECT survey_id, title, end
45 FROM survey_questions
46 WHERE '.$where.';';
47 if ($tpl) {
48 return XDB::iterator($sql);
49 } else {
50 return XDB::iterRow($sql);
51 }
52 }
53 // }}}
54
55 // {{{ static function proposeSurvey() : stores a proposition of survey in database (before validation)
56 public static function proposeSurvey($survey)
57 {
58 $sql = 'INSERT INTO survey_questions
59 SET questions={?},
60 title={?},
61 description={?},
62 author_id={?},
63 end={?},
64 promos={?},
65 valid=0;';
66 $data = $survey->storeArray();
67 return XDB::execute($sql, serialize($survey), $data['question'], $data['comment'], S::v('uid'), $data['end'], $data['promos']);
68 }
69 // }}}
70
71 // {{{ static function updateSurvey() : updates a survey in database (before validation)
72 public static function updateSurvey($survey, $sid)
73 {
74 $sql = 'UPDATE survey_questions
75 SET questions={?},
76 title={?},
77 description={?},
78 end={?},
79 promos={?}
80 WHERE survey_id={?};';
81 $data = $survey->storeArray();
82 return XDB::execute($sql, serialize($survey), $data['question'], $data['comment'], $data['end'], $data['promos'], $sid);
83 }
84 // }}}
85
86 // {{{ static function retrieveSurvey() : gets a survey in database (and unserialize the survey object structure)
87 public static function retrieveSurvey($sid)
88 {
89 $sql = 'SELECT questions, title, description, end, promos, valid
90 FROM survey_questions
91 WHERE survey_id={?}';
92 $res = XDB::query($sql, $sid);
93 $data = $res->fetchOneAssoc();
94 $survey = unserialize($data['questions']);
95 if (isset($data['end'])) {
96 $data['end'] = preg_replace('#^(\d{4})-(\d{2})-(\d{2})$#', '\3/\2/\1', $data['end']);
97 }
98 $survey->update(array('question' => $data['title'], 'comment' => $data['description'], 'end' => $data['end'], 'promos' => $data['promos']));
99 $survey->setValid($data['valid']);
100 return $survey;
101 }
102 // }}}
103
104 // {{{ static function retrieveSurveyInfo() : gets information about a survey (title, description, end date, restrictions) but does not unserialize the survey object structure
105 public static function retrieveSurveyInfo($sid)
106 {
107 $sql = 'SELECT title, description, end, promos, valid
108 FROM survey_questions
109 WHERE survey_id={?}';
110 $res = XDB::query($sql, $sid);
111 return $res->fetchOneAssoc();
112 }
113 // }}}
114
115 // {{{ static function validateSurvey() : validates a survey
116 public static function validateSurvey($sid)
117 {
118 $sql = 'UPDATE survey_questions
119 SET valid=1
120 WHERE survey_id={?};';
121 return XDB::execute($sql, $sid);
122 }
123 // }}}
124
125 // {{{ static function deleteSurvey() : deletes a survey (and all its votes)
126 public static function deleteSurvey($sid)
127 {
128 $sql1 = 'DELETE FROM survey_questions
129 WHERE survey_id={?};';
130 $sql2 = 'DELETE FROM survey_answers
131 WHERE survey_id={?};';
132 $sql3 = 'DELETE FROM survey_votes
133 WHERE survey_id={?};';
134 return (XDB::execute($sql1, $sid) && XDB::execute($sql2, $sid) && XDB::execute($sql3, $sid));
135 }
136 // }}}
137}
138// }}}
139
140// {{{ abstract class SurveyQuestion
141abstract class SurveyQuestion
142{
143 // {{{ static properties and methods regarding question types
144 private static $types = array('text' => 'texte court',
145 'textarea' => 'texte long',
146 'num' => 'num&#233;rique',
147 'radio' => 'radio',
148 'checkbox' => 'checkbox',
149 'personal' => 'informations personnelles');
150
151 public static function getTypes()
152 {
153 return self::$types;
154 }
155
156 public static function isType($t)
157 {
158 return array_key_exists($t, self::$types);
159 }
160 // }}}
161
162 // {{{ common properties, constructor, and basic methods
163 private $survey_id;
164 private $id;
165 private $question;
166 private $comment;
167
168 protected function SurveyQuestion($i, $args)
169 {
170 $this->id = $i;
171 $this->update($args);
172 }
173
174 protected function update($a)
175 {
176 $this->question = $a['question'];
177 $this->comment = $a['comment'];
178 }
179
180 protected function getId()
181 {
182 return $this->id;
183 }
184
185 abstract protected function getQuestionType();
186 // }}}
187
188 // {{{ tree manipulation methods : not implemented here (but definition needed)
189 protected function addChildNested($i, $c)
190 {
191 return false;
192 }
193
194 protected function addChildAfter($i, $c)
195 {
196 return false;
197 }
198
199 protected function delChild($i)
200 {
201 return false;
202 }
203 // }}}
204
205 // {{{ function edit($i, $a) : searches and edits question $i
206 protected function edit($i, $a)
207 {
208 if ($this->id == $i) {
209 $this->update($a);
210 return true;
211 } else {
212 return false;
213 }
214 }
215 // }}}
216
217 // {{{ functions toArray() and searchToArray($i) : (searches and) converts to array
218 protected function toArray()
219 {
220 return $this->storeArray();
221 }
222
223 protected function searchToArray($i)
224 {
225 if ($this->id == $i) {
226 return $this->storeArray();
227 } else {
228 return null;
229 }
230 }
231
232 protected function storeArray()
233 {
234 return array('type' => $this->getQuestionType(), 'id' => $this->id, 'question' => $this->question, 'comment' => $this->comment);
235 }
236 // }}}
237
238 // {{{ function checkSyntax() : checks question elements (before storing into database)
239 protected function checkSyntax()
240 {
241 return null;
242 }
243 // }}}
244
245 // {{{ function vote() : handles vote
246 protected function checkAnswer($ans)
247 {
248 return "";
249 }
250
251 function vote($sid, $vid, $a)
252 {
253 $ans = $this->checkAnswer($a[$this->getId]);
254 if ($ans != "") {
255 XDB::execute(
256 'INSERT INTO survey_answers
257 SET survey_id = {?},
258 vote_id = {?},
259 question_id = {?},
260 answer = "{?}"', $sid, $vid, $id, $ans);
261 }
262 }
263 // }}}
264}
265// }}}
266
267// {{{ abstract class SurveyTreeable extends SurveyQuestion : questions that allow nested ones
268abstract class SurveyTreeable extends SurveyQuestion
269{
270 // {{{ common properties, constructor
271 private $children;
272
273 protected function SurveyTreeable($i, $args)
274 {
275 parent::__construct($i, $args);
276 $this->children = array();
277 }
278 // }}}
279
280 // {{{ tree manipulation functions : actual implementation
281 protected function hasChild()
282 {
283 return !is_null($this->children) && is_array($this->children);
284 }
285
286 protected function addChildNested($i, $c)
287 {
288 if ($this->getId() == $i) {
289 if ($this->hasChild()) {
290 array_unshift($this->children, $c);
291 } else {
292 $this->children = array($c);
293 }
294 return true;
295 } else {
296 foreach ($this->children as $child) {
297 if ($child->addChildNested($i, $c)) {
298 return true;
299 }
300 }
301 return false;
302 }
303 }
304
305 protected function addChildAfter($i, $c)
306 {
307 $found = false;
308 for ($k = 0; $k < count($this->children); $k++) {
309 if ($this->children[$k]->getId() == $i) {
310 $found = true;
311 break;
312 } else {
313 if ($this->children[$k]->addChildAfter($i, $c)) {
314 return true;
315 }
316 }
317 }
318 if ($found) {
319 array_splice($this->children, $k+1, 0, array($c));
320 return true;
321 }
322 return false;
323 }
324
325 protected function delChild($i)
326 {
327 $found = false;
328 for ($k = 0; $k < count($this->children); $k++) {
329 if ($this->children[$k]->getId() == $i) {
330 $found = true;
331 break;
332 } else {
333 if ($this->children[$k]->delChild($i)) {
334 return true;
335 }
336 }
337 }
338 if ($found) {
339 array_splice($this->children, $k, 1);
340 return true;
341 }
342 return false;
343 }
344 // }}}
345
346 // {{{ function edit() with tree support
347 protected function edit($i, $a)
348 {
349 if ($this->getId() == $i) {
350 $this->update($a);
351 return true;
352 } else {
353 foreach ($this->children as $child) {
354 if ($child->edit($i, $a)) {
355 return true;
356 }
357 }
358 return false;
359 }
360 }
361 // }}}
362
363 // {{{ functions toArray() and searchToArray() with tree support
364 protected function toArray()
365 {
366 if ($this->hasChild()) {
367 $cArr = array();
368 foreach ($this->children as $child) {
369 $cArr[] = $child->toArray();
370 }
371 $a = $this->storeArray();
372 $a['children'] = $cArr;
373 return $a;
374 } else {
375 return $this->storeArray();
376 }
377 }
378
379 protected function searchToArray($i)
380 {
381 if ($this->getId() == $i) {
382 return $this->storeArray();
383 } else {
384 foreach ($this->children as $child) {
385 $a = $child->searchToArray($i);
386 if (!is_null($a) && is_array($a)) {
387 return $a;
388 }
389 }
390 return null;
391 }
392 }
393 // }}}
394
395 // {{{ function checkSyntax()
396 protected function checkSyntax()
397 {
398 $rArr = array();
399 foreach ($this->children as $child) {
400 $a = $child->checkSyntax();
401 if ($a != null) {
402 $rArr[] = $a;
403 }
404 }
405 return (empty($rArr))? null : $rArr;
406 }
407 // }}}
408
409 // {{{ function vote()
410 function vote($sid, $vid, $a)
411 {
412 parent::vote($sid, $vid, $a);
413 if ($this->hasChild()) {
414 foreach ($this->children as $c) {
415 $c->vote($sid, $vid, $a);
416 }
417 }
418 }
419 // }}}
420}
421// }}}
422
423// {{{ class SurveyRoot extends SurveyTreeable : root of any survey, actually the only entry point (no public methods outside this class)
424class SurveyRoot extends SurveyTreeable
425{
426 // {{{ properties, constructor and basic methods
427 private $last_id;
428 private $beginning;
429 private $end;
430 private $promos;
431 private $valid;
432
433 public function SurveyRoot($args)
434 {
435 parent::__construct(0, $args);
436 $this->last_id = 0;
437 }
438
439 public function update($args)
440 {
441 parent::update($args);
442 //$this->beginning = $args['beginning_year'] . "-" . $args['beginning_month'] . "-" . $args['beginning_day'];
443 //$this->end = $args['end_year'] . "-" . $args['end_year'] . "-" . $args['end_day'];
444 if (preg_match('#^\d{2}/\d{2}/\d{4}$#', $args['end'])) {
445 $this->end = preg_replace('#^(\d{2})/(\d{2})/(\d{4})$#', '\3-\2-\1', $args['end']);
446 } else {
447 $this->end = (preg_match('#^\d{4}-\d{2}-\d{2}$#', $args['end']))? $args['end'] : '#';
448 }
449 $this->promos = ($args['promos'] == '' || preg_match('#^(\d{4}-?|(\d{4})?-\d{4})(,(\d{4}-?|(\d{4})?-\d{4}))*$#', $args['promos']))? $args['promos'] : '#';
450 }
451
452 private function getNextId()
453 {
454 $this->last_id++;
455 return $this->last_id;
456 }
457
458 public function setValid($v)
459 {
460 $this->valid = (intval($v) != 0);
461 }
462
463 public function isValid()
464 {
465 return $this->valid;
466 }
467
468 protected function getQuestionType()
469 {
470 return "root";
471 }
472 // }}}
473
474 // {{{ function factory($type, $args) : builds a question according to the given type
475 public function factory($t, $args)
476 {
477 $i = $this->getNextId();
478 switch ($t) {
479 case 'text':
480 return new SurveyText($i, $args);
481 case 'textarea':
482 return new SurveyTextarea($i, $args);
483 case 'num':
484 return new SurveyNum($i, $args);
485 case 'radio':
486 return new SurveyRadio($i, $args);
487 case 'checkbox':
488 return new SurveyCheckbox($i, $args);
489 case 'personal':
490 return new SurveyPersonal($i, $args);
491 default:
492 return null;
493 }
494 }
495 // }}}
496
497 // {{{ methods needing public access
498 public function addChildNested($i, $c)
499 {
500 return parent::addChildNested($i, $c);
501 }
502
503 public function addChildAfter($i, $c)
504 {
505 return parent::addChildAfter($i, $c);
506 }
507
508 public function delChild($i)
509 {
510 return parent::delChild($i);
511 }
512
513 public function edit($i, $a)
514 {
515 return parent::edit($i, $a);
516 }
517
518 public function toArray()
519 {
520 return parent::toArray();
521 }
522
523 public function searchToArray($i)
524 {
525 return parent::searchToArray($i);
526 }
527 // }}}
528
529 // {{{ function storeArray()
530 public function storeArray()
531 {
532 $rArr = parent::storeArray();
533 $rArr['beginning'] = $this->beginning;
534 $rArr['end'] = $this->end;
535 $rArr['promos'] = $this->promos;
536 return $rArr;
537 }
538 // }}}
539
540 // {{{ function checkSyntax()
541 private static $errorMessages = array(
542 "dateformat" => "la date de fin de sondage est mal formatt&#233;e : elle doit respecter la syntaxe dd/mm/aaaa",
543 "datepassed" => "la date de fin de sondage est d&#233;j&#224; d&#233;pass&#233;e : vous devez pr&#233;ciser une date future",
544 "promoformat" => "les restrictions &#224; certaines promotions sont mal formatt&#233;es"
545 );
546
547 public function checkSyntax()
548 {
549 $rArr = parent::checkSyntax();
550 if (!preg_match('#^\d{4}-\d{2}-\d{2}$#', $this->end)) {
551 $rArr[] = array('question' => $this->getId(), 'error' => self::$errorMessages["dateformat"]);
552 } else {
553 if (strtotime($this->end) - time() <= 0) {
554 $rArr[] = array('question' => $this->getId(), 'error' => self::$errorMessages["datepassed"]);
555 }
556 }
557 if ($this->promos != '' && !preg_match('#^(\d{4}-?|(\d{4})?-\d{4})(,(\d{4}-?|(\d{4})?-\d{4}))*$#', $this->promos)) {
558 $rArr[] = array('question' => $this->getId(), 'error' => self::$errorMessages["promoformat"]);
559 }
560 return (empty($rArr))? null : $rArr;
561 }
562 // }}}
563}
564// }}}
565
566// {{{ abstract class SurveySimple extends SurveyQuestion : "opened" questions
567abstract class SurveySimple extends SurveyQuestion
568{
569 protected function checkAnswer($ans)
570 {
571 return $ans;
572 }
573}
574
575// {{{ class SurveyText extends SurveySimple : simple text field, allowing a few words
576class SurveyText extends SurveySimple
577{
578 protected function getQuestionType()
579 {
580 return "text";
581 }
582}
583// }}}
584
585// {{{ class SurveyTextarea extends SurveySimple : textarea field, allowing longer comments
586class SurveyTextarea extends SurveySimple
587{
588 protected function getQuestionType()
589 {
590 return "textarea";
591 }
592}
593// }}}
594
595// {{{ class SurveyNum extends SurveySimple : allows numerical answers
596class SurveyNum extends SurveySimple
597{
598 protected function checkAnswer($ans)
599 {
600 return intval($ans);
601 }
602
603 protected function getQuestionType()
604 {
605 return "num";
606 }
607}
608// }}}
609// }}}
610
611// {{{ abstract class SurveyList extends SurveyTreeable : restricted questions that allows only a list of possible answers
612abstract class SurveyList extends SurveyTreeable
613{
614 private $choices;
615
616 protected function update($args)
617 {
618 parent::update($args);
619 $this->choices = explode('|', $args['options']);
620 }
621
622 protected function storeArray()
623 {
624 $rArr = parent::storeArray();
625 $rArr['choices'] = $this->choices;
626 $rArr['options'] = implode('|', $this->choices);
627 return $rArr;
628 }
629
630}
631
632// {{{ class SurveyRadio extends SurveyList : radio question, allows one answer among the list offered
633class SurveyRadio extends SurveyList
634{
635 protected function checkAnswer($ans)
636 {
637 return (in_array($ans, $this->choices)) ? $ans : "";
638 }
639
640 protected function getQuestionType()
641 {
642 return "radio";
643 }
644}
645// }}}
646
647// {{{ class SurveyCheckbox extends SurveyList : checkbox question, allows any number of answers among the list offered
648class SurveyCheckbox extends SurveyList
649{
650 protected function checkAnswer($ans)
651 {
652 $rep = "";
653 foreach ($this->choices as $key => $value) {
654 if (array_key_exists($key,$v[$id]) && $v[$id][$key]) {
655 $rep .= "|" . $key;
656 }
657 }
658 $rep = (strlen($rep) >= 4) ? substr($rep, 4) : "";
659 return $rep;
660 }
661
662 protected function getQuestionType()
663 {
664 return "checkbox";
665 }
666}
667// }}}
668// }}}
669
670// {{{ class SurveyPersonal extends SurveyQuestion : allows easy and verified access to user's personal data (promotion, name...)
671class SurveyPersonal extends SurveyQuestion
672{
673 private $perm;
674
675 protected function update($args)
676 {
677 $args['question'] = "Informations personnelles";
678 parent::update($args);
679 $this->perm['promo'] = isset($args['promo'])? 1 : 0;
680 $this->perm['name'] = isset($args['name'])? 1 : 0;
681 }
682
683 protected function checkAnswer($ans)
684 {
685 if (intval($ans) == 1) {
686 // requete mysql qvb
687 return "";
688 } else {
689 return "";
690 }
691 }
692
693 protected function getQuestionType()
694 {
695 return "personal";
696 }
697
698 protected function storeArray()
699 {
700 $a = parent::storeArray();
701 $a['promo'] = $this->perm['promo'];
702 $a['name'] = $this->perm['name'];
703 return $a;
704 }
705}
706// }}}
707
708// vim:set et sw=4 sts=4 sws=4 foldmethod=marker enc=utf-8:
709?>