MDL-52830 mod_quiz: New Web Service mod_quiz_get_attempt_summary

This commit is contained in:
Juan Leyva
2016-03-11 11:06:50 +01:00
parent 4c08658a23
commit bc9733e710
4 changed files with 147 additions and 29 deletions
+85 -14
View File
@@ -787,6 +787,9 @@ class mod_quiz_external extends external_api {
$attemptobj = quiz_attempt::create($params['attemptid']);
// If the attempt is now overdue, or abandoned, deal with that.
$attemptobj->handle_if_time_expired(time(), true);
$context = context_module::instance($attemptobj->get_cm()->id);
self::validate_context($context);
@@ -831,23 +834,27 @@ class mod_quiz_external extends external_api {
$accessmanager->notify_preflight_check_passed($params['attemptid']);
}
// Check if the page is out of range.
if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number');
}
// Prevent out of sequence access.
if ($attemptobj->get_currentpage() != $params['page']) {
if ($attemptobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ && $attemptobj->get_currentpage() > $params['page']) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
if (isset($params['page'])) {
// Check if the page is out of range.
if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number');
}
}
// Check slots.
$slots = $attemptobj->get_slots($params['page']);
// Prevent out of sequence access.
if ($attemptobj->get_currentpage() != $params['page']) {
if ($attemptobj->get_navigation_method() == QUIZ_NAVMETHOD_SEQ &&
$attemptobj->get_currentpage() > $params['page']) {
if (empty($slots)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access');
}
}
// Check slots.
$slots = $attemptobj->get_slots($params['page']);
if (empty($slots)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
}
}
return array($attemptobj, $messages);
@@ -1002,4 +1009,68 @@ class mod_quiz_external extends external_api {
);
}
/**
* Describes the parameters for get_attempt_summary.
*
* @return external_external_function_parameters
* @since Moodle 3.1
*/
public static function get_attempt_summary_parameters() {
return new external_function_parameters (
array(
'attemptid' => new external_value(PARAM_INT, 'attempt id'),
'preflightdata' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'),
'value' => new external_value(PARAM_RAW, 'data value'),
)
), 'Preflight required data (like passwords)', VALUE_DEFAULT, array()
)
)
);
}
/**
* Returns a summary of a quiz attempt before it is submitted.
*
* @param int $attemptid attempt id
* @param int $preflightdata preflight required data (like passwords)
* @return array of warnings and the attempt summary data for each question
* @since Moodle 3.1
*/
public static function get_attempt_summary($attemptid, $preflightdata = array()) {
$warnings = array();
$params = array(
'attemptid' => $attemptid,
'preflightdata' => $preflightdata,
);
$params = self::validate_parameters(self::get_attempt_summary_parameters(), $params);
list($attemptobj, $messages) = self::validate_attempt($params);
$result = array();
$result['warnings'] = $warnings;
$result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all');
return $result;
}
/**
* Describes the get_attempt_summary return value.
*
* @return external_single_structure
* @since Moodle 3.1
*/
public static function get_attempt_summary_returns() {
return new external_single_structure(
array(
'questions' => new external_multiple_structure(self::question_structure()),
'warnings' => new external_warnings(),
)
);
}
}
+9
View File
@@ -91,4 +91,13 @@ $functions = array(
'capabilities' => 'mod/quiz:attempt',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_quiz_get_attempt_summary' => array(
'classname' => 'mod_quiz_external',
'methodname' => 'get_attempt_summary',
'description' => 'Returns a summary of a quiz attempt before it is submitted.',
'type' => 'read',
'capabilities' => 'mod/quiz:attempt',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);
+52 -14
View File
@@ -764,21 +764,7 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$this->assertEquals($attempt->id, $result[0]->get_attempt()->id);
$this->assertEquals([], $result[1]);
// Try to open attempt in closed quiz.
$quiz->timeopen = time() - WEEKSECS;
$quiz->timeclose = time() - DAYSECS;
$DB->update_record('quiz', $quiz);
try {
testable_mod_quiz_external::validate_attempt($params);
$this->fail('Exception expected due to passed dates.');
} catch (moodle_quiz_exception $e) {
$this->assertEquals('attempterror', $e->errorcode);
}
// Page out of range.
$quiz->timeopen = 0;
$quiz->timeclose = 0;
$DB->update_record('quiz', $quiz);
$params['page'] = 4;
try {
@@ -788,6 +774,18 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
$this->assertEquals('Invalid page number', $e->errorcode);
}
$params['page'] = 0;
// Try to open attempt in closed quiz.
$quiz->timeopen = time() - WEEKSECS;
$quiz->timeclose = time() - DAYSECS;
$DB->update_record('quiz', $quiz);
try {
testable_mod_quiz_external::validate_attempt($params);
$this->fail('Exception expected due to passed dates.');
} catch (moodle_quiz_exception $e) {
$this->assertEquals('attemptalreadyclosed', $e->errorcode);
}
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$attemptobj->process_finish(time(), false);
@@ -912,4 +910,44 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
}
/**
* Test get_attempt_summary
*/
public function test_get_attempt_summary() {
// Create a new quiz with one attempt started.
list($quiz, $context, $quizobj, $attempt, $attemptobj) = $this->create_quiz_with_questions(true);
$this->setUser($this->student);
$result = mod_quiz_external::get_attempt_summary($attempt->id);
$result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_summary_returns(), $result);
// Check the state, flagged and mark data is correct.
$this->assertEquals('todo', $result['questions'][0]['state']);
$this->assertEquals('todo', $result['questions'][1]['state']);
$this->assertEquals(1, $result['questions'][0]['number']);
$this->assertEquals(2, $result['questions'][1]['number']);
$this->assertFalse($result['questions'][0]['flagged']);
$this->assertFalse($result['questions'][1]['flagged']);
$this->assertEmpty($result['questions'][0]['mark']);
$this->assertEmpty($result['questions'][1]['mark']);
// Submit a response for the first question.
$tosubmit = array(1 => array('answer' => '3.14'));
$attemptobj->process_submitted_actions(time(), false, $tosubmit);
$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.
$this->assertEquals('complete', $result['questions'][0]['state']);
$this->assertEquals('todo', $result['questions'][1]['state']);
$this->assertEquals(1, $result['questions'][0]['number']);
$this->assertEquals(2, $result['questions'][1]['number']);
$this->assertFalse($result['questions'][0]['flagged']);
$this->assertFalse($result['questions'][1]['flagged']);
$this->assertEmpty($result['questions'][0]['mark']);
$this->assertEmpty($result['questions'][1]['mark']);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2015111608;
$plugin->version = 2015111609;
$plugin->requires = 2015111000;
$plugin->component = 'mod_quiz';
$plugin->cron = 60;