More stuff for the new surveys.
authorFlorent Bruneau <florent.bruneau@polytechnique.org>
Sun, 7 Nov 2010 21:54:18 +0000 (22:54 +0100)
committerFlorent Bruneau <florent.bruneau@polytechnique.org>
Sun, 14 Nov 2010 20:27:24 +0000 (21:27 +0100)
Signed-off-by: Florent Bruneau <florent.bruneau@polytechnique.org>
core
modules/survey.php
modules/survey/answer.inc.php [new file with mode: 0644]
modules/survey/question.inc.php [new file with mode: 0644]
modules/survey/survey.inc.php
modules/survey/text.inc.php
templates/survey/question.section.tpl [new file with mode: 0644]
templates/survey/question.text.tpl [new file with mode: 0644]
templates/survey/vote.tpl
upgrade/1.1.0/10_surveys.sql

diff --git a/core b/core
index 23749c6..cb22cf2 160000 (submodule)
--- a/core
+++ b/core
@@ -1 +1 @@
-Subproject commit 23749c61b65d441b631e791005277ec1c69f4c83
+Subproject commit cb22cf2a3b34e4f263dcfb6b51396111f8d6b785
index 1c451bc..7a34573 100644 (file)
@@ -85,6 +85,18 @@ class SurveyModule extends PLModule
         if (!$survey->canSee(S::user())) {
             return PL_FORBIDDEN;
         }
+        if (Post::has('vote')) {
+            $answers = Post::v('qid');
+            $vote = $survey->vote(S::user(), $answers);
+            if (is_null($vote)) {
+                $page->kill("Tu n'as pas le droit de voter à ce sondage.");
+            } else if ($vote->inError()) {
+                $page->trigError("Certaines réponses sont invalides et doivent être corrigées");
+            } else {
+                $vote->insert(true);
+                $page->trigSuccess("Ton vote a été enregistré");
+            }
+        }
         $page->assign('survey', $survey);
     }
 }
diff --git a/modules/survey/answer.inc.php b/modules/survey/answer.inc.php
new file mode 100644 (file)
index 0000000..8781a6b
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2010 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program is distributed in the hope that it will be useful,        *
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
+ *  GNU General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+class SurveyVote extends PlDBTableEntry
+{
+    protected $survey;
+    protected $user;
+
+    private $answers = array();
+    private $fetchAnswers;
+
+    public function __construct(Survey $survey, User $user)
+    {
+        parent::__construct('survey_votes');
+        $this->survey = $survey;
+        $this->user = $user;
+        $this->sid = $survey->id;
+    }
+
+    protected function postSave()
+    {
+        Platal::assert(!is_null($this->vid), "Cannot process a vote without its identifier");
+        XDB::execute("REPLACE INTO  survey_voters (sid, uid, vid)
+                            VALUES  ({?}, {?}, {?})",
+                     $this->survey->id, $this->user->id(),
+                     $this->survey->flags->hasFlag('anonymous') ? null : $this->vid);
+
+        /* Save answers */
+        $selector = new SurveyAnswer($this);
+        $selector->delete();
+
+        $answers = array();
+        foreach ($this->answers as $key=>$answer) {
+            if (!is_null($answer)) {
+                $answer->vid = $this->vid;
+                $answers[] = $answer;
+            }
+        }
+        PlDBTableEntry::insertBatch($answers);
+        return true;
+    }
+
+    protected function postFetch()
+    {
+        $selector = new SurveyAnswer($this);
+        foreach ($selector as $answer) {
+            $question = $this->survey->questionForId($answer->qid);
+            $this->answers[$answer->qid] = $answer;
+        }
+        return true;
+    }
+
+    public function inError()
+    {
+        foreach ($this->answers as $answer) {
+            if ($answer->inError !== false) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public function getAnswer(SurveyQuestion $question)
+    {
+        if (!isset($this->answers[$question->qid])) {
+            $val = new SurveyAnswer($this);
+            $val->qid = $question->qid;
+            $this->answers[$question->qid] = $val;
+        }
+        return $this->answers[$question->qid];
+    }
+
+    public function export()
+    {
+        $export = array();
+        foreach ($this->answers as $qid=>$answer) {
+            $export[$qid] = $answer->export();
+        }
+        return $export;
+    }
+
+    public static function getVote(Survey $survey, User $user, $fetchAnswers = true)
+    {
+        $vid = XDB::query('SELECT  vid
+                             FROM  survey_voters
+                            WHERE  sid = {?} AND uid = {?}',
+                          $survey->id, $user->id());
+        if ($vid->numRows() == 0) {
+            $vote = new SurveyVote($survey, $user);
+            $vote->fetchAnswers = $fetchAnswers;
+            return $vote;
+        }
+        $vid = $vid->fetchOneCell();
+        if (is_null($vid)) {
+            /* User already vote, but survey is anonymous and the vote
+             * cannot be changed
+             */
+            return null;
+        }
+        $vote = new SurveyVote($survey, $user);
+        $vote->vid = $vid;
+        $vote->fetchAnswers = $fetchAnswers;
+        $vote->fetch();
+        return $vote;
+    }
+}
+
+class SurveyAnswer extends PlDBTableEntry
+{
+    public $inError = false;
+    public $vote;
+
+    public function __construct(SurveyVote $vote)
+    {
+        parent::__construct('survey_vote_answers');
+        $this->registerFieldFormatter('answer', 'JSonFieldFormatter');
+        $this->vote = $vote;
+        if (!is_null($vote->vid)) {
+            $this->vid = $vote->vid;
+        }
+    }
+
+    protected function preSave()
+    {
+        Platal::assert(!$this->inError, "Cannot save an invalid answer");
+        $this->sid = $this->vote->sid;
+        $this->vid = $this->vote->vid;
+        return true;
+    }
+
+    public function export()
+    {
+        $export = array();
+        if (!is_null($this->answer)) {
+            $export['value'] = $this->answer->export();
+        }
+        if ($this->inError !== false) {
+            $export['error'] = $this->inError;
+        }
+        return $export;
+    }
+}
+
+// vim:set et sw=4 sts=4 ts=4 foldmethod=marker enc=utf-8:
+?>
diff --git a/modules/survey/question.inc.php b/modules/survey/question.inc.php
new file mode 100644 (file)
index 0000000..9a92620
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/***************************************************************************
+ *  Copyright (C) 2003-2010 Polytechnique.org                              *
+ *  http://opensource.polytechnique.org/                                   *
+ *                                                                         *
+ *  This program is free software; you can redistribute it and/or modify   *
+ *  it under the terms of the GNU General Public License as published by   *
+ *  the Free Software Foundation; either version 2 of the License, or      *
+ *  (at your option) any later version.                                    *
+ *                                                                         *
+ *  This program is distributed in the hope that it will be useful,        *
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
+ *  GNU General Public License for more details.                           *
+ *                                                                         *
+ *  You should have received a copy of the GNU General Public License      *
+ *  along with this program; if not, write to the Free Software            *
+ *  Foundation, Inc.,                                                      *
+ *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
+ ***************************************************************************/
+
+interface SurveyQuestionContainer
+{
+    public function addQuestion(SurveyQuestion $question, $pos = null);
+    public function newQuestion($type, $pos = null);
+    public function reassignQuestionIds();
+}
+
+class SurveyQuestion extends PlDBTableEntry
+{
+    protected $survey;
+    protected $parentQuestion;
+
+    public function __construct(Survey $survey)
+    {
+        parent::__construct('survey_questions');
+        $this->registerFieldFormatter('parameters', 'JSonFieldFormatter');
+        $this->survey = $survey;
+    }
+
+    public function typedInstance()
+    {
+        $instance = self::instanceForType($this->survey, $this->type);
+        $instance->copy($this);
+        return $instance;
+    }
+
+    public static function instanceForType(Survey $survey, $type)
+    {
+        require_once dirname(__FILE__) . '/' . $type . '.inc.php';
+        $class = 'SurveyQuestion' . $type;
+        return new $class($survey);
+    }
+
+    public function voteTemplate()
+    {
+        return 'survey/question.' . $this->type . '.tpl';
+    }
+
+    public function editTemplate()
+    {
+        return 'survey/edit.' . $this->type . '.tpl';
+    }
+
+    protected function buildAnswer(SurveyAnswer $answer, PlDict $answers)
+    {
+        Platal::assert(false, "This should not happen");
+    }
+
+    public function vote(SurveyVote $vote, PlDict $answers)
+    {
+        if ($this->flags->hasFlag('noanswer')) {
+            if ($answers->has($this->qid)) {
+                throw new Exception("Des réponses ont été données à une question n'en attendant pas");
+            }
+            return null;
+        }
+        $answer = $vote->getAnswer($this);
+        if (is_null($answer)) {
+            return null;
+        }
+        if (!$this->buildAnswer($answer, $answers)) {
+            return $answer;
+        }
+        if ($this->flags->hasFlag('mandatory') && is_null($answer->answer)) {
+            $answer->inError = 'Tu dois répondre à cette question';
+        }
+        return $answer;
+    }
+}
+
+class SurveyQuestionGroup extends SurveyQuestion implements SurveyQuestionContainer
+{
+    public $children = array();
+
+    public function __construct(Survey $survey)
+    {
+        parent::__construct($survey);
+    }
+
+    public function addQuestion(SurveyQuestion $question, $pos = null)
+    {
+        $question->parentQuestion = $this;
+        if (is_null($pos)) {
+            $this->children[] = $question;
+        } else {
+            array_splice($this->children, $pos, 0, $question);
+        }
+    }
+
+    public function newQuestion($type, $pos = null)
+    {
+        $question = SurveyQuestion::instanceForType($this->survey, $type);
+        $this->addQuestion($question, $pos);
+        return $question;
+    }
+
+    public function reassignQuestionIds()
+    {
+        $id = $this->qid + 1;
+        foreach ($this->children as $question) {
+            $question->qid = $id;
+            if ($question instanceof SurveyQuestionContainer) {
+                $id = $question->reassignQuestionIds();
+            } else {
+                $id++;
+            }
+        }
+        return $id;
+    }
+
+    protected function postSave()
+    {
+        foreach ($this->children as $question) {
+            $question->sid = $this->sid;
+            $question->parent = $this->qid;
+            $question->insert();
+        }
+    }
+
+    public function export()
+    {
+        $export = parent::export();
+        $export['children'] = array();
+        foreach ($this->children as $child) {
+            $export['children'][] = $child->export();
+        }
+        return $export;
+    }
+
+    public function vote(SurveyVote $vote, PlDict $answers)
+    {
+        $a = parent::vote($vote, $answers);
+        foreach ($this->children as $child) {
+            $child->vote($vote, $answers);
+        }
+        return $a;
+    }
+
+    public function child($qid)
+    {
+        $prev = null;
+        foreach ($this->children as $question) {
+            if ($qid == $question->qid) {
+                return $question;
+            } else if ($qid < $question->qid) {
+                Platal::assert($prev instanceof SurveyQuestionGroup);
+                return $prev->child($qid);
+            }
+            $prev = $question;
+        }
+        Platal::assert($prev instanceof SurveyQuestionGroup);
+        return $prev->child($qid);
+    }
+}
+
+// vim:set et sw=4 sts=4 ts=4 foldmethod=marker enc=utf-8:
+?>
index c96a44d..3812d2e 100644 (file)
  *  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA                *
  ***************************************************************************/
 
+require_once dirname(__FILE__) . '/question.inc.php';
+require_once dirname(__FILE__) . '/answer.inc.php';
+
 class Survey extends PlDBTableEntry implements SurveyQuestionContainer
 {
     private $fetchQuestions = true;
     public $questions = array();
+    public $viewerFilter = null;
+    public $voterFilter = null;
 
     public function __construct()
     {
@@ -34,6 +39,16 @@ class Survey extends PlDBTableEntry implements SurveyQuestionContainer
 
     protected function postFetch()
     {
+        if (!is_null($this->voters)) {
+            $this->voterFilter = UserFilter::fromExportedConditions($this->voters);
+        } else {
+            $this->voterFilter = null;
+        }
+        if (!is_null($this->viewers)) {
+            $this->viewerFilter = UserFilter::fromExportedConditions($this->viewers);
+        } else {
+            $this->viewerFilter = null;
+        }
         if (!$this->fetchQuestions) {
             return true;
         }
@@ -60,6 +75,21 @@ class Survey extends PlDBTableEntry implements SurveyQuestionContainer
         return true;
     }
 
+    protected function preSave()
+    {
+        if (!is_null($this->voterFilter)) {
+            $this->voters = $this->voterFilter->exportConditions();
+        } else {
+            $this->voters = null;
+        }
+        if (!is_null($this->viewerFilter)) {
+            $this->viewers = $this->viewerFilter->exportConditions();
+        } else {
+            $this->viewers = null;
+        }
+        return true;
+    }
+
     protected function postSave()
     {
         $questions = array();
@@ -91,6 +121,24 @@ class Survey extends PlDBTableEntry implements SurveyQuestionContainer
         return $question;
     }
 
+    public function questionForId($qid)
+    {
+        $prev = null;
+        foreach ($this->questions as $question) {
+            if ($qid == $question->qid) {
+                return $question;
+            } else if ($qid < $question->qid) {
+                Platal::assert($prev instanceof SurveyQuestionGroup,
+                               "Id gap must be caused by question groups");
+                return $prev->child($qid);
+            }
+            $prev = $question;
+        }
+        Platal::assert($prev instanceof SurveyQuestionGroup,
+                       "Id gap must be caused by question groups");
+        return $prev->child($qid);
+    }
+
     public function reassignQuestionIds()
     {
         $id = 0;
@@ -115,14 +163,79 @@ class Survey extends PlDBTableEntry implements SurveyQuestionContainer
         return $export;
     }
 
+    /* Return an indicator of the progression of the survey:
+     *  negative values means 'the survey is not started'
+     *  0 means 'the survey is in progress'
+     *  positive values means 'the survey expired'
+     */
+    public function progression()
+    {
+        if (!$this->flags->hasFlag('validated')) {
+            return -2;
+        }
+        $now = time();
+        if ($this->begin->format('U') > $now) {
+            return -1;
+        }
+        if ($this->end->format('U') <= $now) {
+            return 1;
+        }
+        return 0;
+    }
+
     public function canSee(User $user)
     {
-        if ($this->uid == $user->id() || $user->hasFlag('admin')) {
+        if ($this->canSeeResults($user) || $this->canVote($user)) {
             return true;
         }
         return false;
     }
 
+    public function canSeeResults(User $user)
+    {
+        if ($user->id() == $this->uid || $user->hasFlag('admin')) {
+            return true;
+        }
+        if (is_null($this->viewerFilter)) {
+            return $this->viewerFilter->checkUser($user);
+        }
+        return false;
+    }
+
+    public function canVote(User $user)
+    {
+        $status = $this->progression();
+        if ($status < 0) {
+            return "Ce sondage n'est pas encore commencé";
+        } else if ($status > 0) {
+            return "Ce sondage est terminé";
+        }
+        if (!is_null($this->voterFilter) && !$this->voterFilter->checkUser($user)) {
+            return "Ce sondage ne s'adresse pas à toi";
+        }
+        $vote = SurveyVote::getVote($this, $user, false);
+        if (is_null($vote)) {
+            return "Tu as déjà voté à ce sondage.";
+        }
+        return true;
+    }
+
+    public function vote(User $user, array $answers)
+    {
+        if (!$this->canVote($user)) {
+            return array('survey' => "Tu n'es pas autorisé à voter à ce sondage.");
+        }
+        $vote = SurveyVote::getVote($this, $user);
+        if (is_null($vote)) {
+            return $vote;
+        }
+        $answers = new PlDict($answers);
+        foreach ($this->questions as $question) {
+            $question->vote($vote, $answers);
+        }
+        return $vote;
+    }
+
     public static function get($name, $fetchQuestions = true)
     {
         if (is_array($name)) {
@@ -161,99 +274,5 @@ class ShortNameFieldValidator implements PlDBTableFieldValidator
     }
 }
 
-interface SurveyQuestionContainer
-{
-    public function addQuestion(SurveyQuestion $question, $pos = null);
-    public function newQuestion($type, $pos = null);
-    public function reassignQuestionIds();
-}
-
-class SurveyQuestion extends PlDBTableEntry
-{
-    protected $survey;
-    protected $parentQuestion;
-
-    public function __construct(Survey $survey)
-    {
-        parent::__construct('survey_questions');
-        $this->registerFieldFormatter('parameters', 'JSonFieldFormatter');
-        $this->survey = $survey;
-    }
-
-    public function typedInstance()
-    {
-        $instance = self::instanceForType($this->survey, $this->type);
-        $instance->copy($this);
-        return $instance;
-    }
-
-    public static function instanceForType(Survey $survey, $type)
-    {
-        require_once dirname(__FILE__) . '/' . $type . '.inc.php';
-        $class = 'SurveyQuestion' . $type;
-        return new $class($survey);
-    }
-}
-
-class SurveyQuestionGroup extends SurveyQuestion implements SurveyQuestionContainer
-{
-    public $children = array();
-
-    public function __construct(Survey $survey)
-    {
-        parent::__construct($survey);
-    }
-
-    public function addQuestion(SurveyQuestion $question, $pos = null)
-    {
-        $question->parentQuestion = $this;
-        if (is_null($pos)) {
-            $this->children[] = $question;
-        } else {
-            array_splice($this->children, $pos, 0, $question);
-        }
-    }
-
-    public function newQuestion($type, $pos = null)
-    {
-        $question = SurveyQuestion::instanceForType($this->survey, $type);
-        $this->addQuestion($question, $pos);
-        return $question;
-    }
-
-    public function reassignQuestionIds()
-    {
-        $id = $this->qid + 1;
-        foreach ($this->children as $question) {
-            $question->qid = $id;
-            if ($question instanceof SurveyQuestionContainer) {
-                $id = $question->reassignQuestionIds();
-            } else {
-                $id++;
-            }
-        }
-        return $id;
-    }
-
-    protected function postSave()
-    {
-        foreach ($this->children as $question) {
-            $question->sid = $this->sid;
-            $question->parent = $this->qid;
-            $question->insert();
-        }
-    }
-
-    public function export()
-    {
-        $export = parent::export();
-        $export['children'] = array();
-        foreach ($this->children as $child) {
-            $export['children'][] = $child->export();
-        }
-        return $export;
-    }
-}
-
 // vim:set et sw=4 sts=4 ts=4 foldmethod=marker enc=utf-8:
 ?>
index 5bb65d0..3bb8770 100644 (file)
@@ -26,6 +26,17 @@ class SurveyQuestionText extends SurveyQuestion
         parent::__construct($survey);
         $this->type = "text";
     }
+
+    protected function buildAnswer(SurveyAnswer $answer, PlDict $data)
+    {
+        $value = $data->t($this->qid);
+        if (!empty($value)) {
+            $answer->answer = array('text' => $value);
+        } else {
+            $answer->answer = null;
+        }
+        return true;
+    }
 }
 
 // vim:set et sw=4 sts=4 ts=4 foldmethod=marker enc=utf-8:
diff --git a/templates/survey/question.section.tpl b/templates/survey/question.section.tpl
new file mode 100644 (file)
index 0000000..acdf836
--- /dev/null
@@ -0,0 +1,31 @@
+{**************************************************************************}
+{*                                                                        *}
+{*  Copyright (C) 2003-2010 Polytechnique.org                             *}
+{*  http://opensource.polytechnique.org/                                  *}
+{*                                                                        *}
+{*  This program is free software; you can redistribute it and/or modify  *}
+{*  it under the terms of the GNU General Public License as published by  *}
+{*  the Free Software Foundation; either version 2 of the License, or     *}
+{*  (at your option) any later version.                                   *}
+{*                                                                        *}
+{*  This program is distributed in the hope that it will be useful,       *}
+{*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *}
+{*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *}
+{*  GNU General Public License for more details.                          *}
+{*                                                                        *}
+{*  You should have received a copy of the GNU General Public License     *}
+{*  along with this program; if not, write to the Free Software           *}
+{*  Foundation, Inc.,                                                     *}
+{*  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA               *}
+{*                                                                        *}
+{**************************************************************************}
+
+<fieldset>
+  <legend>{$question->label}</legend>
+
+{foreach from=$question->children item=child}
+  {include file=$child->voteTemplate() question=$child}
+{/foreach}
+</fieldset>
+
+{* vim:set et sw=2 sts=2 ts=8 enc=utf-8: *}
diff --git a/templates/survey/question.text.tpl b/templates/survey/question.text.tpl
new file mode 100644 (file)
index 0000000..a843c01
--- /dev/null
@@ -0,0 +1,28 @@
+{**************************************************************************}
+{*                                                                        *}
+{*  Copyright (C) 2003-2010 Polytechnique.org                             *}
+{*  http://opensource.polytechnique.org/                                  *}
+{*                                                                        *}
+{*  This program is free software; you can redistribute it and/or modify  *}
+{*  it under the terms of the GNU General Public License as published by  *}
+{*  the Free Software Foundation; either version 2 of the License, or     *}
+{*  (at your option) any later version.                                   *}
+{*                                                                        *}
+{*  This program is distributed in the hope that it will be useful,       *}
+{*  but WITHOUT ANY WARRANTY; without even the implied warranty of        *}
+{*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *}
+{*  GNU General Public License for more details.                          *}
+{*                                                                        *}
+{*  You should have received a copy of the GNU General Public License     *}
+{*  along with this program; if not, write to the Free Software           *}
+{*  Foundation, Inc.,                                                     *}
+{*  59 Temple Place, Suite 330, Boston, MA  02111-1307  USA               *}
+{*                                                                        *}
+{**************************************************************************}
+
+<div>
+  <div>{$question->label}</div>
+  <input type="text" name="qid[{$question->qid}]" value="" />
+</div>
+
+{* vim:set et sw=2 sts=2 ts=8 enc=utf-8: *}
index 6ba0d81..9c927ae 100644 (file)
 
 <p>{$survey->description|miniwiki}</p>
 
+<div>
+<form action="survey/vote/{$survey->shortname}" method="post">
+  {foreach from=$survey->questions item=question}
+    {include file=$question->voteTemplate() question=$question}
+  {/foreach}
+
+  <div class="center">
+    {xsrf_token_field}
+    <input type="submit" name="vote" value="Enregister mon vote" />
+  </div>
+</form>
+</div>
+
 {* vim:set et sw=2 sts=2 ts=8 enc=utf-8: *}
index 010a72b..e6e5f47 100644 (file)
@@ -16,12 +16,11 @@ CREATE TABLE surveys (
     description TEXT NOT NULL,
     begin       DATE NOT NULL,
     end         DATE NOT NULL,
-    anonymous   TINYINT(1) DEFAULT 0,
 
     voters      TEXT DEFAULT NULL COMMENT "Filter users who can vote",
     viewers     TEXT DEFAULT NULL COMMENT "Filter users who can see the results",
 
-    flags       SET('validated'),
+    flags       SET('validated', 'anonymous'),
 
     PRIMARY KEY id (id),
     UNIQUE KEY shortname (shortname),
@@ -47,10 +46,10 @@ CREATE TABLE survey_questions (
 ) ENGINE=InnoDB, CHARSET=utf8, COMMENT="Describe the questions of the surveys";
 
 CREATE TABLE survey_votes (
+    vid         INT(11) UNSIGNED NOT NULL auto_increment,
     sid         INT(11) UNSIGNED NOT NULL,
-    vid         INT(11) UNSIGNED NOT NULL,
 
-    PRIMARY KEY id (sid, vid),
+    PRIMARY KEY vid (vid),
     FOREIGN KEY (sid) REFERENCES surveys (id)
                       ON UPDATE CASCADE
                       ON DELETE CASCADE
@@ -72,12 +71,15 @@ CREATE TABLE survey_voters (
 
 CREATE TABLE survey_vote_answers (
     sid         INT(11) UNSIGNED NOT NULL,
-    vid         INT(11) UNSIGNED NOT NULL,
     qid         INT(11) UNSIGNED NOT NULL,
+    vid         INT(11) UNSIGNED NOT NULL,
 
     answer      TEXT DEFAULT NULL,
 
     PRIMARY KEY id (sid, vid, qid),
+    FOREIGN KEY (vid) REFERENCES survey_votes (vid)
+                      ON UPDATE CASCADE
+                      ON DELETE CASCADE,
     FOREIGN KEY (sid, qid) REFERENCES survey_questions (sid, qid)
                            ON UPDATE CASCADE
                            ON DELETE CASCADE