Merge branch 'MDL-34640' of git://github.com/timhunt/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 3 Apr 2013 05:57:02 +0000 (13:57 +0800)
committerDan Poltawski <dan@moodle.com>
Wed, 3 Apr 2013 05:57:02 +0000 (13:57 +0800)
question/engine/datalib.php
question/engine/questionattempt.php
question/engine/questionattemptstep.php
question/engine/tests/questionattemptstep_db_test.php [new file with mode: 0644]
question/engine/tests/questionattemptstep_test.php

index b58c05e..cfd0af2 100644 (file)
@@ -206,6 +206,8 @@ class question_engine_data_mapper {
     public function load_question_attempt_step($stepid) {
         $records = $this->db->get_recordset_sql("
 SELECT
+    quba.contextid,
+    COALLESCE(q.qtype, 'missingtype') AS qtype,
     qas.id AS attemptstepid,
     qas.questionattemptid,
     qas.sequencenumber,
@@ -216,7 +218,10 @@ SELECT
     qasd.name,
     qasd.value
 
-FROM {question_attempt_steps} qas
+FROM      {question_attempt_steps}     qas
+JOIN      {question_attempts}          qa   ON qa.id              = qas.questionattemptid
+JOIN      {question_usages}            quba ON quba.id            = qa.questionusageid
+LEFT JOIN {question}                   q    ON q.id               = qa.questionid
 LEFT JOIN {question_attempt_step_data} qasd ON qasd.attemptstepid = qas.id
 
 WHERE
@@ -1199,6 +1204,21 @@ class question_engine_unit_of_work implements question_usage_observer {
 }
 
 
+/**
+ * The interface implemented by {@link question_file_saver} and {@link question_file_loader}.
+ *
+ * @copyright  2012 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+interface question_response_files {
+    /**
+     * Get the files that were submitted.
+     * @return array of stored_files objects.
+     */
+    public function get_files();
+}
+
+
 /**
  * This class represents the promise to save some files from a particular draft
  * file area into a particular file area. It is used beause the necessary
@@ -1210,7 +1230,7 @@ class question_engine_unit_of_work implements question_usage_observer {
  * @copyright  2011 The Open University
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class question_file_saver {
+class question_file_saver implements question_response_files {
     /** @var int the id of the draft file area to save files from. */
     protected $draftitemid;
     /** @var string the owning component name. */
@@ -1290,6 +1310,73 @@ class question_file_saver {
         file_save_draft_area_files($this->draftitemid, $context->id,
                 $this->component, $this->filearea, $itemid);
     }
+
+    /**
+     * Get the files that were submitted.
+     * @return array of stored_files objects.
+     */
+    public function get_files() {
+        global $USER;
+
+        $fs = get_file_storage();
+        $usercontext = context_user::instance($USER->id);
+
+        return $fs->get_area_files($usercontext->id, 'user', 'draft',
+                $this->draftitemid, 'sortorder, filepath, filename', false);
+    }
+}
+
+
+/**
+ * This class is the mirror image of {@link question_file_saver}. It allows
+ * files to be accessed again later (e.g. when re-grading) using that same
+ * API as when doing the original grading.
+ *
+ * @copyright  2012 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_file_loader implements question_response_files {
+    /** @var question_attempt_step the step that these files belong to. */
+    protected $step;
+
+    /** @var string the field name for these files - which is used to construct the file area name. */
+    protected $name;
+
+    /**
+    * @var string the value to stored in the question_attempt_step_data to
+     * represent these files.
+    */
+    protected $value;
+
+    /** @var int the context id that the files belong to. */
+    protected $contextid;
+
+    /**
+     * Constuctor.
+     * @param question_attempt_step $step the step that these files belong to.
+     * @param string $name string the field name for these files - which is used to construct the file area name.
+     * @param string $value the value to stored in the question_attempt_step_data to
+     *      represent these files.
+     * @param int $contextid the context id that the files belong to.
+     */
+    public function __construct(question_attempt_step $step, $name, $value, $contextid) {
+        $this->step = $step;
+        $this->name = $name;
+        $this->value = $value;
+        $this->contextid = $contextid;
+    }
+
+    public function __toString() {
+        return $this->value;
+    }
+
+    /**
+     * Get the files that were submitted.
+     * @return array of stored_files objects.
+     */
+    public function get_files() {
+        return $this->step->get_qt_files($this->name, $this->contextid);
+    }
 }
 
 
index ae4be38..06afd49 100644 (file)
@@ -1285,7 +1285,7 @@ class question_attempt {
         $autosavedsequencenumber = null;
         while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) {
             $sequencenumber = $record->sequencenumber;
-            $nextstep = question_attempt_step::load_from_records($records, $record->attemptstepid);
+            $nextstep = question_attempt_step::load_from_records($records, $record->attemptstepid, $qa->get_question()->get_type_name());
 
             if ($sequencenumber < 0) {
                 if (!$autosavedstep) {
index 5cbd5c8..90ee1d5 100644 (file)
@@ -370,9 +370,11 @@ class question_attempt_step {
      * Create a question_attempt_step from records loaded from the database.
      * @param Iterator $records Raw records loaded from the database.
      * @param int $stepid The id of the records to extract.
+     * @param string $qtype The question type of which this is an attempt.
+     *      If not given, each record must include a qtype field.
      * @return question_attempt_step The newly constructed question_attempt_step.
      */
-    public static function load_from_records($records, $attemptstepid) {
+    public static function load_from_records($records, $attemptstepid, $qtype = null) {
         $currentrec = $records->current();
         while ($currentrec->attemptstepid != $attemptstepid) {
             $records->next();
@@ -384,6 +386,7 @@ class question_attempt_step {
         }
 
         $record = $currentrec;
+        $contextid = null;
         $data = array();
         while ($currentrec && $currentrec->attemptstepid == $attemptstepid) {
             if (!is_null($currentrec->name)) {
@@ -403,6 +406,22 @@ class question_attempt_step {
         if (!is_null($record->fraction)) {
             $step->fraction = $record->fraction + 0;
         }
+
+        // This next chunk of code requires getting $contextid and $qtype here.
+        // Somehow, we need to get that information to this point by modifying
+        // all the paths by which this method can be called.
+        // Can we only return files when it's possible? Should there be some kind of warning?
+        if (is_null($qtype)) {
+            $qtype = $record->qtype;
+        }
+        foreach (question_bank::get_qtype($qtype)->response_file_areas() as $area) {
+            if (empty($step->data[$area])) {
+                continue;
+            }
+
+            $step->data[$area] = new question_file_loader($step, $area, $step->data[$area], $record->contextid);
+        }
+
         return $step;
     }
 }
diff --git a/question/engine/tests/questionattemptstep_db_test.php b/question/engine/tests/questionattemptstep_db_test.php
new file mode 100644 (file)
index 0000000..a9b1581
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle 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 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle 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 Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains tests for the question_attempt_step class.
+ *
+ * @package    moodlecore
+ * @subpackage questionengine
+ * @copyright  2009 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(dirname(__FILE__) . '/../lib.php');
+require_once(dirname(__FILE__) . '/helpers.php');
+
+
+/**
+ * Unit tests for the loading data into the {@link question_attempt_step} class.
+ *
+ * @copyright  2009 The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_attempt_step_db_test extends data_loading_method_test_base {
+    public function test_load_with_data() {
+        $records = new question_test_recordset(array(
+            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value', 'qtype', 'contextid'),
+            array(             1,                   1,                0,  'todo',       null,    1256228502,       13,   null,    null, 'description', 1),
+            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,    'x',     'a', 'description', 1),
+            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   '_y',    '_b', 'description', 1),
+            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   '-z',    '!c', 'description', 1),
+            array(             2,                   1,                1,  'complete',   null,    1256228505,       13, '-_t',    '!_d', 'description', 1),
+            array(             3,                   1,                2,  'gradedright', 1.0,    1256228515,       13, '-finish',  '1', 'description', 1),
+        ));
+
+        $step = question_attempt_step::load_from_records($records, 2);
+        $this->assertEquals(question_state::$complete, $step->get_state());
+        $this->assertNull($step->get_fraction());
+        $this->assertEquals(1256228505, $step->get_timecreated());
+        $this->assertEquals(13, $step->get_user_id());
+        $this->assertEquals(array('x' => 'a', '_y' => '_b', '-z' => '!c', '-_t' => '!_d'), $step->get_all_data());
+    }
+
+    public function test_load_without_data() {
+        $records = new question_test_recordset(array(
+            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value', 'contextid'),
+            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   null,    null, 1),
+        ));
+
+        $step = question_attempt_step::load_from_records($records, 2, 'description');
+        $this->assertEquals(question_state::$complete, $step->get_state());
+        $this->assertNull($step->get_fraction());
+        $this->assertEquals(1256228505, $step->get_timecreated());
+        $this->assertEquals(13, $step->get_user_id());
+        $this->assertEquals(array(), $step->get_all_data());
+    }
+
+    public function test_load_dont_be_too_greedy() {
+        $records = new question_test_recordset(array(
+            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value', 'contextid'),
+            array(             1,                   1,                0,  'todo',       null,    1256228502,       13,    'x',  'right', 1),
+            array(             2,                   2,                0,  'complete',   null,    1256228505,       13,    'x',  'wrong', 1),
+        ));
+
+        $step = question_attempt_step::load_from_records($records, 1, 'description');
+        $this->assertEquals(array('x' => 'right'), $step->get_all_data());
+    }
+}
index 6ac1b67..8878f8f 100644 (file)
@@ -129,56 +129,3 @@ class question_attempt_step_test extends advanced_testcase {
 
     }
 }
-
-
-/**
- * Unit tests for the loading data into the {@link question_attempt_step} class.
- *
- * @copyright  2009 The Open University
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class question_attempt_step_db_test extends data_loading_method_test_base {
-    public function test_load_with_data() {
-        $records = new question_test_recordset(array(
-            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value'),
-            array(             1,                   1,                0,  'todo',       null,    1256228502,       13,   null,    null),
-            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,    'x',     'a'),
-            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   '_y',    '_b'),
-            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   '-z',    '!c'),
-            array(             2,                   1,                1,  'complete',   null,    1256228505,       13, '-_t',    '!_d'),
-            array(             3,                   1,                2,  'gradedright', 1.0,    1256228515,       13, '-finish',  '1'),
-        ));
-
-        $step = question_attempt_step::load_from_records($records, 2);
-        $this->assertEquals(question_state::$complete, $step->get_state());
-        $this->assertNull($step->get_fraction());
-        $this->assertEquals(1256228505, $step->get_timecreated());
-        $this->assertEquals(13, $step->get_user_id());
-        $this->assertEquals(array('x' => 'a', '_y' => '_b', '-z' => '!c', '-_t' => '!_d'), $step->get_all_data());
-    }
-
-    public function test_load_without_data() {
-        $records = new question_test_recordset(array(
-            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value'),
-            array(             2,                   1,                1,  'complete',   null,    1256228505,       13,   null,    null),
-        ));
-
-        $step = question_attempt_step::load_from_records($records, 2);
-        $this->assertEquals(question_state::$complete, $step->get_state());
-        $this->assertNull($step->get_fraction());
-        $this->assertEquals(1256228505, $step->get_timecreated());
-        $this->assertEquals(13, $step->get_user_id());
-        $this->assertEquals(array(), $step->get_all_data());
-    }
-
-    public function test_load_dont_be_too_greedy() {
-        $records = new question_test_recordset(array(
-            array('attemptstepid', 'questionattemptid', 'sequencenumber', 'state', 'fraction', 'timecreated', 'userid', 'name', 'value'),
-            array(             1,                   1,                0,  'todo',       null,    1256228502,       13,    'x',     'right'),
-            array(             2,                   2,                0,  'complete',   null,    1256228505,       13,    'x',     'wrong'),
-        ));
-
-        $step = question_attempt_step::load_from_records($records, 1);
-        $this->assertEquals(array('x' => 'right'), $step->get_all_data());
-    }
-}
\ No newline at end of file