Merge remote branch 'origin/xorg/1.0.2/master' into xorg/master
[platal.git] / modules / survey / survey.inc.php
1 <?php
2 /***************************************************************************
3 * Copyright (C) 2003-2011 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 require_once dirname(__FILE__) . '/question.inc.php';
23 require_once dirname(__FILE__) . '/answer.inc.php';
24
25 class Survey extends PlDBTableEntry implements SurveyQuestionContainer
26 {
27 private $fetchQuestions = true;
28 public $questions = array();
29 public $viewerFilter = null;
30 public $voterFilter = null;
31
32 public function __construct()
33 {
34 parent::__construct('surveys');
35 $this->registerFieldValidator('shortname', 'ShortNameFieldValidator');
36 $this->registerFieldFormatter('voters', 'JSonFieldFormatter');
37 $this->registerFieldFormatter('viewers', 'JSonFieldFormatter');
38 }
39
40 protected function postFetch()
41 {
42 if (!is_null($this->voters)) {
43 $this->voterFilter = UserFilter::fromExportedConditions($this->voters);
44 } else {
45 $this->voterFilter = null;
46 }
47 if (!is_null($this->viewers)) {
48 $this->viewerFilter = UserFilter::fromExportedConditions($this->viewers);
49 } else {
50 $this->viewerFilter = null;
51 }
52 if (!$this->fetchQuestions) {
53 return true;
54 }
55 $selector = new SurveyQuestion($this);
56 $selector->sid = $this->id;
57
58 $stack = array();
59 foreach ($selector as $question) {
60 $question = $question->typedInstance();
61 if (is_null($question->parent)) {
62 $this->addQuestion($question);
63 } else {
64 $pos = count($stack) - 1;
65 while ($stack[$pos]->qid != $question->parent) {
66 --$pos;
67 array_pop($stack);
68 }
69 Platal::assert(count($stack) > 0, "Invalid question structure");
70 Platal::assert($stack[$pos] instanceof SurveyQuestionContainer, "Invalid question type");
71 $stack[$pos]->addQuestion($question);
72 }
73 array_push($stack, $question);
74 }
75 return true;
76 }
77
78 protected function preSave()
79 {
80 if (!is_null($this->voterFilter)) {
81 $this->voters = $this->voterFilter->exportConditions();
82 } else {
83 $this->voters = null;
84 }
85 if (!is_null($this->viewerFilter)) {
86 $this->viewers = $this->viewerFilter->exportConditions();
87 } else {
88 $this->viewers = null;
89 }
90 return true;
91 }
92
93 protected function postSave()
94 {
95 $questions = array();
96 $selector = new SurveyQuestion($this);
97 $selector->sid = $this->id;
98 $selector->delete();
99
100 $this->reassignQuestionIds();
101 foreach ($this->questions as $question) {
102 $question->sid = $this->id;
103 $question->insert();
104 }
105 }
106
107 public function clearQuestions()
108 {
109 $this->fetchQuestions = true;
110 $this->questions = array();
111 }
112
113 public function addQuestion(SurveyQuestion $question, $pos = null)
114 {
115 $question->parent = null;
116 if (is_null($pos)) {
117 $this->questions[] = $question;
118 } else {
119 array_splice($this->questions, $pos, 0, $question);
120 }
121 }
122
123 public function newQuestion($type, $pos = null)
124 {
125 $question = SurveyQuestion::instanceForType($this, $type);
126 $this->addQuestion($question, $pos);
127 return $question;
128 }
129
130 public function questionForId($qid)
131 {
132 $prev = null;
133 foreach ($this->questions as $question) {
134 if ($qid == $question->qid) {
135 return $question;
136 } else if ($qid < $question->qid) {
137 Platal::assert($prev instanceof SurveyQuestionGroup,
138 "Id gap must be caused by question groups");
139 return $prev->child($qid);
140 }
141 $prev = $question;
142 }
143 Platal::assert($prev instanceof SurveyQuestionGroup,
144 "Id gap must be caused by question groups");
145 return $prev->child($qid);
146 }
147
148 public function reassignQuestionIds()
149 {
150 $id = 0;
151 foreach ($this->questions as $question) {
152 $question->qid = $id;
153 if ($question instanceof SurveyQuestionContainer) {
154 $id = $question->reassignQuestionIds();
155 } else {
156 $id++;
157 }
158 }
159 return $id;
160 }
161
162 public function export()
163 {
164 $export = parent::export();
165 $export['questions'] = $this->exportQuestions();
166 return $export;
167 }
168
169 public function exportQuestions()
170 {
171 $export = array();
172 foreach ($this->questions as $question) {
173 $export[] = $question->export();
174 }
175 return $export;
176 }
177
178 public function exportQuestionsToJSON()
179 {
180 return json_encode($this->exportQuestions());
181 }
182
183 /* Return an indicator of the progression of the survey:
184 * negative values means 'the survey is not started'
185 * 0 means 'the survey is in progress'
186 * positive values means 'the survey expired'
187 */
188 public function progression()
189 {
190 if (!$this->flags->hasFlag('validated')) {
191 return -2;
192 }
193 $now = time();
194 if ($this->begin->format('U') > $now) {
195 return -1;
196 }
197 if ($this->end->format('U') <= $now) {
198 return 1;
199 }
200 return 0;
201 }
202
203 public function canSee(User $user)
204 {
205 if ($this->canSeeResults($user) || $this->canVote($user)) {
206 return true;
207 }
208 return false;
209 }
210
211 public function canSeeResults(User $user)
212 {
213 if ($user->id() == $this->uid || $user->hasFlag('admin')) {
214 return true;
215 }
216 if (is_null($this->viewerFilter)) {
217 return $this->viewerFilter->checkUser($user);
218 }
219 return false;
220 }
221
222 public function canVote(User $user)
223 {
224 $status = $this->progression();
225 if ($status < 0) {
226 return "Ce sondage n'est pas encore commencé";
227 } else if ($status > 0) {
228 return "Ce sondage est terminé";
229 }
230 if (!is_null($this->voterFilter) && !$this->voterFilter->checkUser($user)) {
231 return "Ce sondage ne s'adresse pas à toi";
232 }
233 $vote = SurveyVote::getVote($this, $user, false);
234 if (is_null($vote)) {
235 return "Tu as déjà voté à ce sondage.";
236 }
237 return true;
238 }
239
240 public function vote(User $user, array $answers)
241 {
242 if (!$this->canVote($user)) {
243 return array('survey' => "Tu n'es pas autorisé à voter à ce sondage.");
244 }
245 $vote = SurveyVote::getVote($this, $user);
246 if (is_null($vote)) {
247 return $vote;
248 }
249 $answers = new PlDict($answers);
250 foreach ($this->questions as $question) {
251 $question->vote($vote, $answers);
252 }
253 return $vote;
254 }
255
256 public static function get($name, $fetchQuestions = true)
257 {
258 if (is_array($name)) {
259 $name = $name[0];
260 }
261 $survey = new Survey();
262 $survey->fetchQuestions = $fetchQuestions;
263 if (can_convert_to_integer($name)) {
264 $survey->id = $name;
265 } else {
266 $survey->shortname = $name;
267 }
268 if (!$survey->fetch()) {
269 return null;
270 }
271 return $survey;
272 }
273
274 public static function iterActive()
275 {
276 $survey = new Survey();
277 $survey->fetchQuestions = false;
278 return $survey->iterateOnCondition('begin <= CURDATE() AND end >= CURDATE()
279 AND FIND_IN_SET(\'validated\', flags)');
280 }
281 }
282
283 class ShortNameFieldValidator implements PlDBTableFieldValidator
284 {
285 public function __construct(PlDBTableField $field, $value)
286 {
287 if (can_convert_to_integer($value) || !preg_match('/^[a-z0-9]+[-_\.a-z0-9]*[a-z0-9]+$/i', $value)) {
288 throw new PlDBBadValueException($value, $field,
289 'The shortname can only contain alphanumerical caracters, dashes, underscores and dots');
290 }
291 }
292 }
293
294 // vim:set et sw=4 sts=4 ts=4 foldmethod=marker enc=utf-8:
295 ?>