diff --git a/mod/quiz/classes/external.php b/mod/quiz/classes/external.php index 11835cca62b..9087bc48f74 100644 --- a/mod/quiz/classes/external.php +++ b/mod/quiz/classes/external.php @@ -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(), + ) + ); + } + } diff --git a/mod/quiz/db/services.php b/mod/quiz/db/services.php index 8ee0227c0b3..9f3e84f9333 100644 --- a/mod/quiz/db/services.php +++ b/mod/quiz/db/services.php @@ -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) + ), ); diff --git a/mod/quiz/tests/external_test.php b/mod/quiz/tests/external_test.php index bd8a74133c8..f2c42c86302 100644 --- a/mod/quiz/tests/external_test.php +++ b/mod/quiz/tests/external_test.php @@ -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']); + + } + } diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 6c6f841352f..c2b32ffdb0f 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2015111608; +$plugin->version = 2015111609; $plugin->requires = 2015111000; $plugin->component = 'mod_quiz'; $plugin->cron = 60;