MDL-54956 quiz: Return response files in attempts

This commit is contained in:
Juan Leyva
2020-09-23 12:52:45 +02:00
parent 611399214b
commit e49ccc92c4
3 changed files with 105 additions and 10 deletions
+38 -6
View File
@@ -877,6 +877,14 @@ class mod_quiz_external extends external_api {
'type' => new external_value(PARAM_ALPHANUMEXT, 'question type, i.e: multichoice'),
'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'),
'html' => new external_value(PARAM_RAW, 'the question rendered'),
'responsefileareas' => new external_multiple_structure(
new external_single_structure(
array(
'area' => new external_value(PARAM_NOTAGS, 'File area name'),
'files' => new external_files('Response files for the question', VALUE_OPTIONAL),
)
), 'Response file areas including files', VALUE_OPTIONAL
),
'sequencecheck' => new external_value(PARAM_INT, 'the number of real steps in this attempt', VALUE_OPTIONAL),
'lastactiontime' => new external_value(PARAM_INT, 'the timestamp of the most recent step in this question attempt',
VALUE_OPTIONAL),
@@ -914,23 +922,47 @@ class mod_quiz_external extends external_api {
$contextid = $attemptobj->get_quizobj()->get_context()->id;
$displayoptions = $attemptobj->get_display_options($review);
$renderer = $PAGE->get_renderer('mod_quiz');
$contextid = $attemptobj->get_quizobj()->get_context()->id;
foreach ($attemptobj->get_slots($page) as $slot) {
$qtype = $attemptobj->get_question_type_name($slot);
$qattempt = $attemptobj->get_question_attempt($slot);
// Get response files (for questions like essay that allows attachments).
$responsefileareas = [];
foreach (question_bank::get_qtype($qtype)->response_file_areas() as $area) {
if ($files = $attemptobj->get_question_attempt($slot)->get_last_qt_files($area, $contextid)) {
$responsefileareas[$area]['area'] = $area;
$responsefileareas[$area]['files'] = [];
foreach ($files as $file) {
$responsefileareas[$area]['files'][] = array(
'filename' => $file->get_filename(),
'fileurl' => $qattempt->get_response_file_url($file),
'filesize' => $file->get_filesize(),
'filepath' => $file->get_filepath(),
'mimetype' => $file->get_mimetype(),
'timemodified' => $file->get_timemodified(),
);
}
}
}
$question = array(
'slot' => $slot,
'type' => $attemptobj->get_question_type_name($slot),
'type' => $qtype,
'page' => $attemptobj->get_question_page($slot),
'flagged' => $attemptobj->is_question_flagged($slot),
'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code(),
'sequencecheck' => $attemptobj->get_question_attempt($slot)->get_sequence_check_count(),
'lastactiontime' => $attemptobj->get_question_attempt($slot)->get_last_step()->get_timecreated(),
'hasautosavedstep' => $attemptobj->get_question_attempt($slot)->has_autosaved_step()
'responsefileareas' => $responsefileareas,
'sequencecheck' => $qattempt->get_sequence_check_count(),
'lastactiontime' => $qattempt->get_last_step()->get_timecreated(),
'hasautosavedstep' => $qattempt->has_autosaved_step()
);
if ($attemptobj->is_real_question($slot)) {
$question['number'] = $attemptobj->get_question_number($slot);
$showcorrectness = $displayoptions->correctness && $attemptobj->get_question_attempt($slot)->has_marks();
$showcorrectness = $displayoptions->correctness && $qattempt->has_marks();
if ($showcorrectness) {
$question['state'] = (string) $attemptobj->get_question_state($slot);
}
@@ -938,7 +970,7 @@ class mod_quiz_external extends external_api {
$question['blockedbyprevious'] = $attemptobj->is_blocked_by_previous_question($slot);
}
if ($displayoptions->marks >= question_display_options::MAX_ONLY) {
$question['maxmark'] = $attemptobj->get_question_attempt($slot)->get_max_mark();
$question['maxmark'] = $qattempt->get_max_mark();
}
if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) {
$question['mark'] = $attemptobj->get_question_mark($slot);
+61 -4
View File
@@ -109,9 +109,11 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
* @param boolean $startattempt whether to start a new attempt
* @param boolean $finishattempt whether to finish the new attempt
* @param string $behaviour the quiz preferredbehaviour, defaults to 'deferredfeedback'.
* @param boolean $includeqattachments whether to include a question that supports attachments, defaults to false.
* @return array array containing the quiz, context and the attempt
*/
private function create_quiz_with_questions($startattempt = false, $finishattempt = false, $behaviour = 'deferredfeedback') {
private function create_quiz_with_questions($startattempt = false, $finishattempt = false, $behaviour = 'deferredfeedback',
$includeqattachments = false) {
// Create a new quiz with attempts.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
@@ -130,6 +132,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$question = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
quiz_add_quiz_question($question->id, $quiz);
if ($includeqattachments) {
$question = $questiongenerator->create_question('essay', null, array('category' => $cat->id, 'attachments' => 1,
'attachmentsrequired' => 1));
quiz_add_quiz_question($question->id, $quiz);
}
$quizobj = quiz::create($quiz->id, $this->student->id);
// Set grade to pass.
@@ -1126,8 +1134,9 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
global $DB;
$timenow = time();
// Create a new quiz with two questions and one attempt started.
list($quiz, $context, $quizobj, $attempt, $attemptobj, $quba) = $this->create_quiz_with_questions(true);
// Create a new quiz with three questions and one attempt started.
list($quiz, $context, $quizobj, $attempt, $attemptobj, $quba) = $this->create_quiz_with_questions(true, false,
'deferredfeedback', true);
// Response for slot 1.
$prefix = $quba->get_field_prefix(1);
@@ -1144,9 +1153,12 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$result = external_api::clean_returnvalue(mod_quiz_external::process_attempt_returns(), $result);
$this->assertEquals(quiz_attempt::IN_PROGRESS, $result['state']);
$result = mod_quiz_external::get_attempt_data($attempt->id, 2);
// Now, get the summary.
$result = mod_quiz_external::get_attempt_summary($attempt->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
$this->assertDebuggingCalled(); // Expect $PAGE->set_url debugging.
// Check it's marked as completed only the first one.
$this->assertEquals('complete', $result['questions'][0]['state']);
@@ -1182,12 +1194,57 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$result = mod_quiz_external::get_attempt_summary($attempt->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
// Check it's marked as completed only the first one.
// Check it's marked as completed the two first questions.
$this->assertEquals('complete', $result['questions'][0]['state']);
$this->assertEquals('complete', $result['questions'][1]['state']);
$this->assertFalse($result['questions'][0]['flagged']);
$this->assertTrue($result['questions'][1]['flagged']);
// Add files in the attachment response.
$draftitemid = file_get_unused_draft_itemid();
$filerecordinline = array(
'contextid' => context_user::instance($this->student->id)->id,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftitemid,
'filepath' => '/',
'filename' => 'faketxt.txt',
);
$fs = get_file_storage();
$fs->create_file_from_string($filerecordinline, 'fake txt contents 1.');
// Last slot.
$prefix = $quba->get_field_prefix(3);
$data = array(
array('name' => 'slots', 'value' => 3),
array('name' => $prefix . ':sequencecheck',
'value' => $attemptobj->get_question_attempt(1)->get_sequence_check_count()),
array('name' => $prefix . 'answer', 'value' => 'Some test'),
array('name' => $prefix . 'answerformat', 'value' => FORMAT_HTML),
array('name' => $prefix . 'attachments', 'value' => $draftitemid),
);
$result = mod_quiz_external::process_attempt($attempt->id, $data);
$result = external_api::clean_returnvalue(mod_quiz_external::process_attempt_returns(), $result);
$this->assertEquals(quiz_attempt::IN_PROGRESS, $result['state']);
// Now, get the summary.
$result = mod_quiz_external::get_attempt_summary($attempt->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
$this->assertEquals('complete', $result['questions'][0]['state']);
$this->assertEquals('complete', $result['questions'][1]['state']);
$this->assertEquals('complete', $result['questions'][2]['state']);
$this->assertFalse($result['questions'][0]['flagged']);
$this->assertTrue($result['questions'][1]['flagged']);
$this->assertFalse($result['questions'][2]['flagged']);
// Check submitted files are there.
$this->assertCount(1, $result['questions'][2]['responsefileareas']);
$this->assertEquals('attachments', $result['questions'][2]['responsefileareas'][0]['area']);
$this->assertCount(1, $result['questions'][2]['responsefileareas'][0]['files']);
$this->assertEquals($filerecordinline['filename'], $result['questions'][2]['responsefileareas'][0]['files'][0]['filename']);
// Finish the attempt.
$sink = $this->redirectMessages();
$result = mod_quiz_external::process_attempt($attempt->id, array(), true);
+6
View File
@@ -1,5 +1,11 @@
This files describes API changes in the quiz code.
=== 3.10 ===
* External functions mod_quiz_external::get_attempt_data, mod_quiz_external::get_attempt_summary
and mod_quiz_external::get_attempt_review now return a new additional optional field:
- responsefileareas: Containing the user responses to questions file area names including files.
=== 3.7 ===
* Quiz_cron() has been removed. Sub-plugins should implemented scheduled tasks, however legacy cron in subplugins are