Merge branch 'MDL-45893-master-fix' of https://github.com/jethac/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 30 Sep 2014 13:35:29 +0000 (14:35 +0100)
committerDan Poltawski <dan@moodle.com>
Tue, 30 Sep 2014 13:35:29 +0000 (14:35 +0100)
13 files changed:
mod/choice/classes/event/answer_submitted.php
mod/choice/classes/event/answer_updated.php
mod/choice/db/install.xml
mod/choice/db/upgrade.php
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/mod_form.php
mod/choice/renderer.php
mod/choice/tests/behat/behat_mod_choice.php
mod/choice/tests/behat/multiple_options.feature [new file with mode: 0644]
mod/choice/tests/events_test.php
mod/choice/version.php
mod/choice/view.php

index 905c17a..323bdc5 100644 (file)
@@ -95,7 +95,7 @@ class answer_submitted extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'c';
         $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
-        $this->data['objecttable'] = 'choice_answers';
+        $this->data['objecttable'] = 'choice';
     }
 
     /**
index a5b04cd..3f205b8 100644 (file)
@@ -95,7 +95,7 @@ class answer_updated extends \core\event\base {
     protected function init() {
         $this->data['crud'] = 'u';
         $this->data['edulevel'] = self::LEVEL_PARTICIPATING;
-        $this->data['objecttable'] = 'choice_answers';
+        $this->data['objecttable'] = 'choice';
     }
 
     /**
index 9259d04..424d58e 100644 (file)
@@ -16,6 +16,7 @@
         <FIELD NAME="showresults" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="allowupdate" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
+        <FIELD NAME="allowmultiple" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="showunanswered" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="limitanswers" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="timeopen" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
index 446b97a..4e5bd4d 100644 (file)
@@ -47,6 +47,21 @@ function xmldb_choice_upgrade($oldversion) {
     // Moodle v2.7.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2014051201) {
+
+        // Define field allowmultiple to be added to choice.
+        $table = new xmldb_table('choice');
+        $field = new xmldb_field('allowmultiple', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'allowupdate');
+
+        // Conditionally launch add field allowmultiple.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Choice savepoint reached.
+        upgrade_mod_savepoint(true, 2014051201, 'choice');
+    }
+
     return true;
 }
 
index 10437c2..cb92597 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['addmorechoices'] = 'Add more choices';
 $string['allowupdate'] = 'Allow choice to be updated';
+$string['allowmultiple'] = 'Allow more than one choice to be selected';
 $string['answered'] = 'Answered';
 $string['completionsubmit'] = 'Show as complete when user makes a choice';
 $string['displayhorizontal'] = 'Display horizontally';
@@ -73,6 +74,7 @@ A choice activity may be used
 $string['modulename_link'] = 'mod/choice/view';
 $string['modulenameplural'] = 'Choices';
 $string['moveselectedusersto'] = 'Move selected users to...';
+$string['multiplenotallowederror'] = 'Multiple answers are not allowed in this choice';
 $string['mustchooseone'] = 'You must choose an answer before saving.  Nothing was saved.';
 $string['noguestchoose'] = 'Sorry, guests are not allowed to make choices.';
 $string['noresultsviewable'] = 'The results are not currently viewable.';
index 8a5fe7c..a2d3a35 100644 (file)
@@ -247,10 +247,27 @@ function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm
     global $DB, $CFG;
     require_once($CFG->libdir.'/completionlib.php');
 
-    $current = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid));
+    if (empty($formanswer)) {
+        print_error('atleastoneoption', 'choice');
+    }
+
+    if (is_array($formanswer)) {
+        if (!$choice->allowmultiple) {
+            print_error('multiplenotallowederror', 'choice');
+        }
+        $formanswers = $formanswer;
+    } else {
+        $formanswers = array($formanswer);
+    }
+
+    $current = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $userid));
     $context = context_module::instance($cm->id);
 
-    $countanswers=0;
+    $choicesexceeded = false;
+    $countanswers = array();
+    foreach ($formanswers as $val) {
+        $countanswers[$val] = 0;
+    }
     if($choice->limitanswers) {
         // Find out whether groups are being used and enabled
         if (groups_get_activity_groupmode($cm) > 0) {
@@ -258,45 +275,75 @@ function choice_user_submit_response($formanswer, $choice, $userid, $course, $cm
         } else {
             $currentgroup = 0;
         }
+
+        list ($insql, $params) = $DB->get_in_or_equal($formanswers, SQL_PARAMS_NAMED);
+
         if($currentgroup) {
             // If groups are being used, retrieve responses only for users in
             // current group
             global $CFG;
-            $answers = $DB->get_records_sql("
-SELECT
-    ca.*
-FROM
-    {choice_answers} ca
-    INNER JOIN {groups_members} gm ON ca.userid=gm.userid
-WHERE
-    optionid=?
-    AND gm.groupid=?", array($formanswer, $currentgroup));
+
+            $params['groupid'] = $currentgroup;
+            $sql = "SELECT ca.*
+                      FROM {choice_answers} ca
+                INNER JOIN {groups_members} gm ON ca.userid=gm.userid
+                     WHERE optionid $insql
+                       AND gm.groupid= :groupid";
         } else {
             // Groups are not used, retrieve all answers for this option ID
-            $answers = $DB->get_records("choice_answers", array("optionid" => $formanswer));
+            $sql = "SELECT ca.*
+                      FROM {choice_answers} ca
+                     WHERE optionid $insql";
         }
 
+        $answers = $DB->get_records_sql($sql, $params);
         if ($answers) {
             foreach ($answers as $a) { //only return enrolled users.
                 if (is_enrolled($context, $a->userid, 'mod/choice:choose')) {
-                    $countanswers++;
+                    $countanswers[$a->optionid]++;
                 }
             }
         }
-        $maxans = $choice->maxanswers[$formanswer];
+        foreach ($countanswers as $opt => $count) {
+            if ($count > $choice->maxanswers[$opt]) {
+                $choicesexceeded = true;
+                break;
+            }
+        }
     }
 
-    if (!($choice->limitanswers && ($countanswers >= $maxans) )) {
+    // Check the user hasn't exceeded the maximum selections for the choice(s) they have selected.
+    if (!($choice->limitanswers && $choicesexceeded)) {
+        $answersnapshots = array();
         if ($current) {
 
-            $newanswer = $current;
-            $newanswer->optionid = $formanswer;
-            $newanswer->timemodified = time();
-            $DB->update_record("choice_answers", $newanswer);
+            $existingchoices = array();
+            foreach ($current as $c) {
+                if (in_array($c->optionid, $formanswers)) {
+                    $existingchoices[] = $c->optionid;
+                    $DB->set_field('choice_answers', 'timemodified', time(), array('id' => $c->id));
+                    $answersnapshots[] = $c;
+                } else {
+                    $DB->delete_records('choice_answers', array('id' => $c->id));
+                }
+            }
+
+            // Add new ones.
+            foreach ($formanswers as $f) {
+                if (!in_array($f, $existingchoices)) {
+                    $newanswer = new stdClass();
+                    $newanswer->optionid = $f;
+                    $newanswer->choiceid = $choice->id;
+                    $newanswer->userid = $userid;
+                    $newanswer->timemodified = time();
+                    $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
+                    $answersnapshots[] = $newanswer;
+                }
+            }
 
             $eventdata = array();
             $eventdata['context'] = $context;
-            $eventdata['objectid'] = $newanswer->id;
+            $eventdata['objectid'] = $choice->id;
             $eventdata['userid'] = $userid;
             $eventdata['courseid'] = $course->id;
             $eventdata['other'] = array();
@@ -304,17 +351,24 @@ WHERE
             $eventdata['other']['optionid'] = $formanswer;
 
             $event = \mod_choice\event\answer_updated::create($eventdata);
-            $event->add_record_snapshot('choice_answers', $newanswer);
             $event->add_record_snapshot('course', $course);
             $event->add_record_snapshot('course_modules', $cm);
+            $event->add_record_snapshot('choice', $choice);
+            foreach ($answersnapshots as $record) {
+                $event->add_record_snapshot('choice_answers', $record);
+            }
             $event->trigger();
         } else {
-            $newanswer = new stdClass();
-            $newanswer->choiceid = $choice->id;
-            $newanswer->userid = $userid;
-            $newanswer->optionid = $formanswer;
-            $newanswer->timemodified = time();
-            $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
+
+            foreach ($formanswers as $answer) {
+                $newanswer = new stdClass();
+                $newanswer->choiceid = $choice->id;
+                $newanswer->userid = $userid;
+                $newanswer->optionid = $answer;
+                $newanswer->timemodified = time();
+                $newanswer->id = $DB->insert_record("choice_answers", $newanswer);
+                $answersnapshots[] = $newanswer;
+            }
 
             // Update completion state
             $completion = new completion_info($course);
@@ -324,21 +378,26 @@ WHERE
 
             $eventdata = array();
             $eventdata['context'] = $context;
-            $eventdata['objectid'] = $newanswer->id;
+            $eventdata['objectid'] = $choice->id;
             $eventdata['userid'] = $userid;
             $eventdata['courseid'] = $course->id;
             $eventdata['other'] = array();
             $eventdata['other']['choiceid'] = $choice->id;
-            $eventdata['other']['optionid'] = $formanswer;
+            $eventdata['other']['optionid'] = $formanswers;
 
             $event = \mod_choice\event\answer_submitted::create($eventdata);
-            $event->add_record_snapshot('choice_answers', $newanswer);
             $event->add_record_snapshot('course', $course);
             $event->add_record_snapshot('course_modules', $cm);
+            $event->add_record_snapshot('choice', $choice);
+            foreach ($answersnapshots as $record) {
+                $event->add_record_snapshot('choice_answers', $record);
+            }
             $event->trigger();
         }
     } else {
-        if (!($current->optionid==$formanswer)) { //check to see if current choice already selected - if not display error
+        // Check to see if current choice already selected - if not display error.
+        $currentids = array_keys($current);
+        if (array_diff($currentids, $formanswers) || array_diff($formanswers, $currentids) ) {
             print_error('choicefull', 'choice');
         }
     }
@@ -350,12 +409,13 @@ WHERE
  * @return void Output is echo'd
  */
 function choice_show_reportlink($user, $cm) {
-    $responsecount =0;
+    $userschosen = array();
     foreach($user as $optionid => $userlist) {
         if ($optionid) {
-            $responsecount += count($userlist);
+            $userschosen = array_merge($userschosen, array_keys($userlist));
         }
     }
+    $responsecount = count(array_unique($userschosen));
 
     echo '<div class="reportlink">';
     echo "<a href=\"report.php?id=$cm->id\">".get_string("viewallresponses", "choice", $responsecount)."</a>";
@@ -433,8 +493,8 @@ function choice_delete_responses($attemptids, $choice, $cm, $course) {
 
     $completion = new completion_info($course);
     foreach($attemptids as $attemptid) {
-        if ($todelete = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $attemptid))) {
-            $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $attemptid));
+        if ($todelete = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid))) {
+            $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'id' => $attemptid));
             // Update completion state
             if ($completion->is_enabled($cm) && $choice->completionsubmit) {
                 $completion->update_state($cm, COMPLETION_INCOMPLETE, $attemptid);
@@ -635,13 +695,18 @@ function choice_get_response_data($choice, $cm, $groupmode) {
 /// Use the responses to move users into the correct column
 
     if ($rawresponses) {
+        $answeredusers = array();
         foreach ($rawresponses as $response) {
             if (isset($allresponses[0][$response->userid])) {   // This person is enrolled and in correct group
                 $allresponses[0][$response->userid]->timemodified = $response->timemodified;
                 $allresponses[$response->optionid][$response->userid] = clone($allresponses[0][$response->userid]);
-                unset($allresponses[0][$response->userid]);   // Remove from unanswered column
+                $allresponses[$response->optionid][$response->userid]->answerid = $response->id;
+                $answeredusers[] = $response->userid;
             }
         }
+        foreach ($answeredusers as $answereduser) {
+            unset($allresponses[0][$answereduser]);
+        }
     }
     return $allresponses;
 }
index 59761bb..a97c7eb 100644 (file)
@@ -33,6 +33,8 @@ class mod_choice_mod_form extends moodleform_mod {
 
         $mform->addElement('selectyesno', 'allowupdate', get_string("allowupdate", "choice"));
 
+        $mform->addElement('selectyesno', 'allowmultiple', get_string('allowmultiple', 'choice'));
+
         $mform->addElement('selectyesno', 'limitanswers', get_string('limitanswers', 'choice'));
         $mform->addHelpButton('limitanswers', 'limitanswers', 'choice');
 
index 21fc8cb..5e2c3cd 100644 (file)
@@ -34,7 +34,7 @@ class mod_choice_renderer extends plugin_renderer_base {
      * @param bool $vertical
      * @return string
      */
-    public function display_options($options, $coursemoduleid, $vertical = false) {
+    public function display_options($options, $coursemoduleid, $vertical = false, $multiple = false) {
         $layoutclass = 'horizontal';
         if ($vertical) {
             $layoutclass = 'vertical';
@@ -50,8 +50,13 @@ class mod_choice_renderer extends plugin_renderer_base {
         foreach ($options['options'] as $option) {
             $choicecount++;
             $html .= html_writer::start_tag('li', array('class'=>'option'));
-            $option->attributes->name = 'answer';
-            $option->attributes->type = 'radio';
+            if ($multiple) {
+                $option->attributes->name = 'answer[]';
+                $option->attributes->type = 'checkbox';
+            } else {
+                $option->attributes->name = 'answer';
+                $option->attributes->type = 'radio';
+            }
             $option->attributes->id = 'choice_'.$choicecount;
 
             $labeltext = $option->text;
@@ -216,7 +221,8 @@ class mod_choice_renderer extends plugin_renderer_base {
                         $userfullname = fullname($user, $choices->fullnamecapability);
                         if ($choices->viewresponsecapability && $choices->deleterepsonsecapability  && $optionid > 0) {
                             $attemptaction = html_writer::label($userfullname, 'attempt-user'.$user->id, false, array('class' => 'accesshide'));
-                            $attemptaction .= html_writer::checkbox('attemptid[]', $user->id,'', null, array('id' => 'attempt-user'.$user->id));
+                            $attemptaction .= html_writer::checkbox('attemptid[]', $user->answerid, '', null,
+                                    array('id' => 'attempt-user'.$user->id));
                             $data .= html_writer::tag('div', $attemptaction, array('class'=>'attemptaction'));
                         }
                         $userimage = $this->output->user_picture($user, array('courseid'=>$choices->courseid));
index 71598e6..ba012aa 100644 (file)
@@ -45,6 +45,7 @@ class behat_mod_choice extends behat_base {
      * @Given /^I choose "(?P<option_string>(?:[^"]|\\")*)" from "(?P<choice_activity_string>(?:[^"]|\\")*)" choice activity$/
      * @param string $option
      * @param string $choiceactivity
+     * @return array
      */
     public function I_choose_option_from_activity($option, $choiceactivity) {
 
@@ -56,4 +57,24 @@ class behat_mod_choice extends behat_base {
         );
     }
 
+    /**
+     * Chooses the specified option from the choice activity named as specified and save the choice.
+     * You should be located in the activity's course page.
+     *
+     * @Given /^I choose options (?P<option_string>"(?:[^"]|\\")*"(?:,"(?:[^"]|\\")*")*) from "(?P<choice_activity_string>(?:[^"]|\\")*)" choice activity$/
+     * @param string $option
+     * @param string $choiceactivity
+     * @return array
+     */
+    public function I_choose_options_from_activity($option, $choiceactivity) {
+        // Escaping again the strings as backslashes have been removed by the automatic transformation.
+        $return = array(new Given('I follow "' . $this->escape($choiceactivity) . '"'));
+        $options = explode('","', trim($option, '"'));
+        foreach ($options as $option) {
+            $return[] = new Given('I set the field "' . $this->escape($option) . '" to "1"');
+        }
+        $return[] = new Given('I press "' . get_string('savemychoice', 'choice') . '"');
+        return $return;
+    }
+
 }
diff --git a/mod/choice/tests/behat/multiple_options.feature b/mod/choice/tests/behat/multiple_options.feature
new file mode 100644 (file)
index 0000000..cd37384
--- /dev/null
@@ -0,0 +1,80 @@
+@mod @mod_choice
+Feature: Multiple option choice response
+  In order to ask questions as a choice of multiple responses
+  As a teacher
+  I need to add choice activities to courses with multiple options enabled
+
+  @javascript
+  Scenario: Complete a choice with multiple options enabled
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Choice" to section "1" and I fill the form with:
+      | Choice name | Choice name |
+      | Description | Choice Description |
+      | Allow more than one choice to be selected | Yes |
+      | option[0] | Option 1 |
+      | option[1] | Option 2 |
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I choose options "Option 1","Option 2" from "Choice name" choice activity
+    Then I should see "Your selection: Option 1; Option 2"
+    And I should see "Your choice has been saved"
+
+  @javascript
+  Scenario: Complete a choice with multiple options enabled and limited responses set
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+      | student2 | Student | 2 | student2@asd.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Choice" to section "1" and I fill the form with:
+      | Choice name | Choice name |
+      | Description | Choice Description |
+      | Allow more than one choice to be selected | Yes |
+      | Limit the number of responses allowed | 1 |
+      | option[0] | Option 1 |
+      | limit[0] | 1 |
+      | option[1] | Option 2 |
+      | limit[1] | 1 |
+      | option[2] | Option 3 |
+      | limit[2] | 1 |
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I choose options "Option 1","Option 2" from "Choice name" choice activity
+    Then I should see "Your selection: Option 1; Option 2"
+    And I should see "Your choice has been saved"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I follow "Choice name"
+    And I should see "Option 1 (Full)"
+    And I should see "Option 2 (Full)"
+    And I should see "Option 3"
+    And the "#choice_1" "css_element" should be disabled
+    And the "#choice_2" "css_element" should be disabled
+    And the "#choice_3" "css_element" should be enabled
\ No newline at end of file
index a8ee8a1..90ce356 100644 (file)
@@ -79,13 +79,46 @@ class mod_choice_events_testcase extends advanced_testcase {
         $this->assertEquals($user->id, $events[0]->userid);
         $this->assertEquals(context_module::instance($this->choice->cmid), $events[0]->get_context());
         $this->assertEquals($this->choice->id, $events[0]->other['choiceid']);
-        $this->assertEquals(3, $events[0]->other['optionid']);
+        $this->assertEquals(array(3), $events[0]->other['optionid']);
         $expected = array($this->course->id, "choice", "choose", 'view.php?id=' . $this->cm->id, $this->choice->id, $this->cm->id);
         $this->assertEventLegacyLogData($expected, $events[0]);
         $this->assertEventContextNotUsed($events[0]);
         $sink->close();
     }
 
+    /**
+     * Test to ensure that multiple choice data is being stored correctly.
+     */
+    public function test_answer_submitted_multiple() {
+        global $DB;
+
+        // Generate user data.
+        $user = $this->getDataGenerator()->create_user();
+
+        // Create multiple choice.
+        $choice = $this->getDataGenerator()->create_module('choice', array('course' => $this->course->id,
+            'allowmultiple' => 1));
+        $cm = $DB->get_record('course_modules', array('id' => $choice->cmid));
+        $context = context_module::instance($choice->cmid);
+
+        // Redirect event.
+        $sink = $this->redirectEvents();
+        choice_user_submit_response(array(1, 3), $choice, $user->id, $this->course, $cm);
+        $events = $sink->get_events();
+
+        // Data checking.
+        $this->assertCount(1, $events);
+        $this->assertInstanceOf('\mod_choice\event\answer_submitted', $events[0]);
+        $this->assertEquals($user->id, $events[0]->userid);
+        $this->assertEquals(context_module::instance($choice->cmid), $events[0]->get_context());
+        $this->assertEquals($choice->id, $events[0]->other['choiceid']);
+        $this->assertEquals(array(1, 3), $events[0]->other['optionid']);
+        $expected = array($this->course->id, "choice", "choose", 'view.php?id=' . $cm->id, $choice->id, $cm->id);
+        $this->assertEventLegacyLogData($expected, $events[0]);
+        $this->assertEventContextNotUsed($events[0]);
+        $sink->close();
+    }
+
     /**
      * Test custom validations.
      */
index 4ea4724..41009cc 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014051200;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2014051201;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014050800;    // Requires this Moodle version
 $plugin->component = 'mod_choice';     // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index 406fd7f..176872d 100644 (file)
 <?php
 
-    require_once("../../config.php");
-    require_once("lib.php");
-    require_once($CFG->libdir . '/completionlib.php');
+require_once("../../config.php");
+require_once("lib.php");
+require_once($CFG->libdir . '/completionlib.php');
 
-    $id         = required_param('id', PARAM_INT);                 // Course Module ID
-    $action     = optional_param('action', '', PARAM_ALPHA);
-    $attemptids = optional_param_array('attemptid', array(), PARAM_INT); // array of attempt ids for delete action
+$id         = required_param('id', PARAM_INT);                 // Course Module ID
+$action     = optional_param('action', '', PARAM_ALPHA);
+$attemptids = optional_param_array('attemptid', array(), PARAM_INT); // array of attempt ids for delete action
 
-    $url = new moodle_url('/mod/choice/view.php', array('id'=>$id));
-    if ($action !== '') {
-        $url->param('action', $action);
-    }
-    $PAGE->set_url($url);
+$url = new moodle_url('/mod/choice/view.php', array('id'=>$id));
+if ($action !== '') {
+    $url->param('action', $action);
+}
+$PAGE->set_url($url);
 
-    if (! $cm = get_coursemodule_from_id('choice', $id)) {
-        print_error('invalidcoursemodule');
-    }
+if (! $cm = get_coursemodule_from_id('choice', $id)) {
+    print_error('invalidcoursemodule');
+}
 
-    if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
-        print_error('coursemisconf');
-    }
+if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
+    print_error('coursemisconf');
+}
 
-    require_course_login($course, false, $cm);
+require_course_login($course, false, $cm);
 
-    if (!$choice = choice_get_choice($cm->instance)) {
-        print_error('invalidcoursemodule');
-    }
+if (!$choice = choice_get_choice($cm->instance)) {
+    print_error('invalidcoursemodule');
+}
 
-    $strchoice = get_string('modulename', 'choice');
-    $strchoices = get_string('modulenameplural', 'choice');
+$strchoice = get_string('modulename', 'choice');
+$strchoices = get_string('modulenameplural', 'choice');
 
-    $context = context_module::instance($cm->id);
+$context = context_module::instance($cm->id);
 
-    if ($action == 'delchoice' and confirm_sesskey() and is_enrolled($context, NULL, 'mod/choice:choose') and $choice->allowupdate) {
-        if ($answer = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id))) {
-            $DB->delete_records('choice_answers', array('id' => $answer->id));
+if ($action == 'delchoice' and confirm_sesskey() and is_enrolled($context, NULL, 'mod/choice:choose') and $choice->allowupdate) {
+    $answercount = $DB->count_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
+    if ($answercount > 0) {
+        $DB->delete_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
 
-            // Update completion state
-            $completion = new completion_info($course);
-            if ($completion->is_enabled($cm) && $choice->completionsubmit) {
-                $completion->update_state($cm, COMPLETION_INCOMPLETE);
-            }
+        // Update completion state
+        $completion = new completion_info($course);
+        if ($completion->is_enabled($cm) && $choice->completionsubmit) {
+            $completion->update_state($cm, COMPLETION_INCOMPLETE);
         }
     }
+}
 
-    $PAGE->set_title($choice->name);
-    $PAGE->set_heading($course->fullname);
+$PAGE->set_title($choice->name);
+$PAGE->set_heading($course->fullname);
 
-    // Mark viewed by user (if required)
-    $completion = new completion_info($course);
-    $completion->set_module_viewed($cm);
+// Mark viewed by user (if required)
+$completion = new completion_info($course);
+$completion->set_module_viewed($cm);
 
 /// Submit any new data if there is any
-    if (data_submitted() && is_enrolled($context, NULL, 'mod/choice:choose') && confirm_sesskey()) {
-        $timenow = time();
-        if (has_capability('mod/choice:deleteresponses', $context)) {
-            if ($action == 'delete') { //some responses need to be deleted
-                choice_delete_responses($attemptids, $choice, $cm, $course); //delete responses.
-                redirect("view.php?id=$cm->id");
-            }
-        }
-        $answer = optional_param('answer', '', PARAM_INT);
-
-        if (empty($answer)) {
-            redirect("view.php?id=$cm->id", get_string('mustchooseone', 'choice'));
-        } else {
-            choice_user_submit_response($answer, $choice, $USER->id, $course, $cm);
-        }
-        echo $OUTPUT->header();
-        echo $OUTPUT->heading(format_string($choice->name), 2, null);
-        echo $OUTPUT->notification(get_string('choicesaved', 'choice'),'notifysuccess');
-    } else {
-        echo $OUTPUT->header();
-        echo $OUTPUT->heading(format_string($choice->name), 2, null);
+if (data_submitted() && is_enrolled($context, NULL, 'mod/choice:choose') && confirm_sesskey()) {
+    $timenow = time();
+    if (has_capability('mod/choice:deleteresponses', $context) && $action == 'delete') {
+        //some responses need to be deleted
+        choice_delete_responses($attemptids, $choice, $cm, $course); //delete responses.
+        redirect("view.php?id=$cm->id");
     }
 
-
-/// Display the choice and possibly results
-    $eventdata = array();
-    $eventdata['objectid'] = $choice->id;
-    $eventdata['context'] = $context;
-
-    $event = \mod_choice\event\course_module_viewed::create($eventdata);
-    $event->add_record_snapshot('course_modules', $cm);
-    $event->add_record_snapshot('course', $course);
-    $event->trigger();
-
-    /// Check to see if groups are being used in this choice
-    $groupmode = groups_get_activity_groupmode($cm);
-
-    if ($groupmode) {
-        groups_get_activity_group($cm, true);
-        groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/choice/view.php?id='.$id);
+    if ($choice->allowmultiple) {
+        $answer = optional_param_array('answer', array(), PARAM_INT);
+    } else {
+        $answer = optional_param('answer', '', PARAM_INT);
     }
-    $allresponses = choice_get_response_data($choice, $cm, $groupmode);   // Big function, approx 6 SQL calls per user
 
-
-    if (has_capability('mod/choice:readresponses', $context)) {
-        choice_show_reportlink($allresponses, $cm);
+    if (empty($answer)) {
+        redirect("view.php?id=$cm->id", get_string('mustchooseone', 'choice'));
+    } else {
+        choice_user_submit_response($answer, $choice, $USER->id, $course, $cm);
     }
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(format_string($choice->name), 2, null);
+    echo $OUTPUT->notification(get_string('choicesaved', 'choice'),'notifysuccess');
+} else {
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(format_string($choice->name), 2, null);
+}
 
-    echo '<div class="clearer"></div>';
 
-    if ($choice->intro) {
-        echo $OUTPUT->box(format_module_intro('choice', $choice, $cm->id), 'generalbox', 'intro');
-    }
-
-    $timenow = time();
-    $current = false;  // Initialise for later
-    //if user has already made a selection, and they are not allowed to update it or if choice is not open, show their selected answer.
-    if (isloggedin() && ($current = $DB->get_record('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id))) &&
-        (empty($choice->allowupdate) || ($timenow > $choice->timeclose)) ) {
-        echo $OUTPUT->box(get_string("yourselection", "choice", userdate($choice->timeopen)).": ".format_string(choice_get_option_text($choice, $current->optionid)), 'generalbox', 'yourselection');
+/// Display the choice and possibly results
+$eventdata = array();
+$eventdata['objectid'] = $choice->id;
+$eventdata['context'] = $context;
+
+$event = \mod_choice\event\course_module_viewed::create($eventdata);
+$event->add_record_snapshot('course_modules', $cm);
+$event->add_record_snapshot('course', $course);
+$event->trigger();
+
+/// Check to see if groups are being used in this choice
+$groupmode = groups_get_activity_groupmode($cm);
+
+if ($groupmode) {
+    groups_get_activity_group($cm, true);
+    groups_print_activity_menu($cm, $CFG->wwwroot . '/mod/choice/view.php?id='.$id);
+}
+$allresponses = choice_get_response_data($choice, $cm, $groupmode);   // Big function, approx 6 SQL calls per user
+
+
+if (has_capability('mod/choice:readresponses', $context)) {
+    choice_show_reportlink($allresponses, $cm);
+}
+
+echo '<div class="clearer"></div>';
+
+if ($choice->intro) {
+    echo $OUTPUT->box(format_module_intro('choice', $choice, $cm->id), 'generalbox', 'intro');
+}
+
+$timenow = time();
+$current = $DB->get_records('choice_answers', array('choiceid' => $choice->id, 'userid' => $USER->id));
+//if user has already made a selection, and they are not allowed to update it or if choice is not open, show their selected answer.
+if (isloggedin() && (!empty($current)) &&
+    (empty($choice->allowupdate) || ($timenow > $choice->timeclose)) ) {
+    $choicetexts = array();
+    foreach ($current as $c) {
+        $choicetexts[] = format_string(choice_get_option_text($choice, $c->optionid));
     }
+    echo $OUTPUT->box(get_string("yourselection", "choice", userdate($choice->timeopen)).": ".implode('; ', $choicetexts), 'generalbox', 'yourselection');
+}
 
 /// Print the form
-    $choiceopen = true;
-    if ($choice->timeclose !=0) {
-        if ($choice->timeopen > $timenow ) {
-            echo $OUTPUT->box(get_string("notopenyet", "choice", userdate($choice->timeopen)), "generalbox notopenyet");
-            echo $OUTPUT->footer();
-            exit;
-        } else if ($timenow > $choice->timeclose) {
-            echo $OUTPUT->box(get_string("expired", "choice", userdate($choice->timeclose)), "generalbox expired");
-            $choiceopen = false;
-        }
+$choiceopen = true;
+if ($choice->timeclose !=0) {
+    if ($choice->timeopen > $timenow ) {
+        echo $OUTPUT->box(get_string("notopenyet", "choice", userdate($choice->timeopen)), "generalbox notopenyet");
+        echo $OUTPUT->footer();
+        exit;
+    } else if ($timenow > $choice->timeclose) {
+        echo $OUTPUT->box(get_string("expired", "choice", userdate($choice->timeclose)), "generalbox expired");
+        $choiceopen = false;
     }
+}
+
+if ( (!$current or $choice->allowupdate) and $choiceopen and is_enrolled($context, NULL, 'mod/choice:choose')) {
+// They haven't made their choice yet or updates allowed and choice is open
+
+    $options = choice_prepare_options($choice, $USER, $cm, $allresponses);
+    $renderer = $PAGE->get_renderer('mod_choice');
+    echo $renderer->display_options($options, $cm->id, $choice->display, $choice->allowmultiple);
+    $choiceformshown = true;
+} else {
+    $choiceformshown = false;
+}
+
+if (!$choiceformshown) {
+    $sitecontext = context_system::instance();
+
+    if (isguestuser()) {
+        // Guest account
+        echo $OUTPUT->confirm(get_string('noguestchoose', 'choice').'<br /><br />'.get_string('liketologin'),
+                     get_login_url(), new moodle_url('/course/view.php', array('id'=>$course->id)));
+    } else if (!is_enrolled($context)) {
+        // Only people enrolled can make a choice
+        $SESSION->wantsurl = qualified_me();
+        $SESSION->enrolcancel = (!empty($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : '';
+
+        $coursecontext = context_course::instance($course->id);
+        $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
+
+        echo $OUTPUT->box_start('generalbox', 'notice');
+        echo '<p align="center">'. get_string('notenrolledchoose', 'choice') .'</p>';
+        echo $OUTPUT->container_start('continuebutton');
+        echo $OUTPUT->single_button(new moodle_url('/enrol/index.php?', array('id'=>$course->id)), get_string('enrolme', 'core_enrol', $courseshortname));
+        echo $OUTPUT->container_end();
+        echo $OUTPUT->box_end();
 
-    if ( (!$current or $choice->allowupdate) and $choiceopen and is_enrolled($context, NULL, 'mod/choice:choose')) {
-    // They haven't made their choice yet or updates allowed and choice is open
-
-        $options = choice_prepare_options($choice, $USER, $cm, $allresponses);
-        $renderer = $PAGE->get_renderer('mod_choice');
-        echo $renderer->display_options($options, $cm->id, $choice->display);
-        $choiceformshown = true;
-    } else {
-        $choiceformshown = false;
     }
+}
 
-    if (!$choiceformshown) {
-        $sitecontext = context_system::instance();
+// print the results at the bottom of the screen
+if ( $choice->showresults == CHOICE_SHOWRESULTS_ALWAYS or
+    ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_ANSWER and $current) or
+    ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_CLOSE and !$choiceopen)) {
 
-        if (isguestuser()) {
-            // Guest account
-            echo $OUTPUT->confirm(get_string('noguestchoose', 'choice').'<br /><br />'.get_string('liketologin'),
-                         get_login_url(), new moodle_url('/course/view.php', array('id'=>$course->id)));
-        } else if (!is_enrolled($context)) {
-            // Only people enrolled can make a choice
-            $SESSION->wantsurl = qualified_me();
-            $SESSION->enrolcancel = (!empty($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : '';
-
-            $coursecontext = context_course::instance($course->id);
-            $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
-
-            echo $OUTPUT->box_start('generalbox', 'notice');
-            echo '<p align="center">'. get_string('notenrolledchoose', 'choice') .'</p>';
-            echo $OUTPUT->container_start('continuebutton');
-            echo $OUTPUT->single_button(new moodle_url('/enrol/index.php?', array('id'=>$course->id)), get_string('enrolme', 'core_enrol', $courseshortname));
-            echo $OUTPUT->container_end();
-            echo $OUTPUT->box_end();
-
-        }
+    if (!empty($choice->showunanswered)) {
+        $choice->option[0] = get_string('notanswered', 'choice');
+        $choice->maxanswers[0] = 0;
     }
+    $results = prepare_choice_show_results($choice, $course, $cm, $allresponses);
+    $renderer = $PAGE->get_renderer('mod_choice');
+    echo $renderer->display_result($results);
 
-    // print the results at the bottom of the screen
-    if ( $choice->showresults == CHOICE_SHOWRESULTS_ALWAYS or
-        ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_ANSWER and $current) or
-        ($choice->showresults == CHOICE_SHOWRESULTS_AFTER_CLOSE and !$choiceopen)) {
-
-        if (!empty($choice->showunanswered)) {
-            $choice->option[0] = get_string('notanswered', 'choice');
-            $choice->maxanswers[0] = 0;
-        }
-        $results = prepare_choice_show_results($choice, $course, $cm, $allresponses);
-        $renderer = $PAGE->get_renderer('mod_choice');
-        echo $renderer->display_result($results);
-
-    } else if (!$choiceformshown) {
-        echo $OUTPUT->box(get_string('noresultsviewable', 'choice'));
-    }
+} else if (!$choiceformshown) {
+    echo $OUTPUT->box(get_string('noresultsviewable', 'choice'));
+}
 
-    echo $OUTPUT->footer();
+echo $OUTPUT->footer();