diff --git a/question/engine/datalib.php b/question/engine/datalib.php index b58c05e0374..cfd0af2bc7f 100644 --- a/question/engine/datalib.php +++ b/question/engine/datalib.php @@ -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); + } } diff --git a/question/engine/questionattempt.php b/question/engine/questionattempt.php index ae4be3820c9..06afd49106e 100644 --- a/question/engine/questionattempt.php +++ b/question/engine/questionattempt.php @@ -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) { diff --git a/question/engine/questionattemptstep.php b/question/engine/questionattemptstep.php index 5cbd5c8a65c..90ee1d521db 100644 --- a/question/engine/questionattemptstep.php +++ b/question/engine/questionattemptstep.php @@ -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 index 00000000000..a9b1581151e --- /dev/null +++ b/question/engine/tests/questionattemptstep_db_test.php @@ -0,0 +1,84 @@ +. + +/** + * 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()); + } +} diff --git a/question/engine/tests/questionattemptstep_test.php b/question/engine/tests/questionattemptstep_test.php index 6ac1b67ee6f..8878f8fd0f8 100644 --- a/question/engine/tests/questionattemptstep_test.php +++ b/question/engine/tests/questionattemptstep_test.php @@ -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