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