MDL-52813 mod_quiz: New Web Service mod_quiz_get_attempt_data

This commit is contained in:
Juan Leyva 2016-01-18 21:02:59 +01:00
parent 1d57e4fea5
commit bc247b0dee
4 changed files with 255 additions and 1 deletions

View File

@ -774,4 +774,232 @@ class mod_quiz_external extends external_api {
);
}
/**
* Utility function for validating a given attempt
*
* @param array $params Array of parameters including the attemptid and preflight data
* @return array containing the attempt object and access messages
* @throws moodle_quiz_exception
* @since Moodle 3.1
*/
protected static function validate_attempt($params) {
global $USER;
$attemptobj = quiz_attempt::create($params['attemptid']);
$context = context_module::instance($attemptobj->get_cm()->id);
self::validate_context($context);
// Check that this attempt belongs to this user.
if ($attemptobj->get_userid() != $USER->id) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt');
}
// General capabilities check.
$ispreviewuser = $attemptobj->is_preview_user();
if (!$ispreviewuser) {
$attemptobj->require_capability('mod/quiz:attempt');
}
// Attempt closed?.
if ($attemptobj->is_finished()) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed');
} else if ($attemptobj->get_state() == quiz_attempt::OVERDUE) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue');
}
// Check the access rules.
$accessmanager = $attemptobj->get_access_manager(time());
$messages = $accessmanager->prevent_access();
if (!$ispreviewuser && $messages) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror');
}
// User submitted data (like the quiz password).
if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) {
$provideddata = array();
foreach ($params['preflightdata'] as $data) {
$provideddata[$data['name']] = $data['value'];
}
$errors = $accessmanager->validate_preflight_check($provideddata, [], $params['attemptid']);
if (!empty($errors)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), array_shift($errors));
}
// Pre-flight check passed.
$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');
}
}
// Check slots.
$slots = $attemptobj->get_slots($params['page']);
if (empty($slots)) {
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound');
}
return array($attemptobj, $messages);
}
/**
* Describes a single question structure.
*
* @return external_single_structure the question structure
* @since Moodle 3.1
*/
private static function question_structure() {
return new external_single_structure(
array(
'slot' => new external_value(PARAM_INT, 'slot number'),
'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'),
'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'),
'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL),
'state' => new external_value(PARAM_ALPHA, 'the state where the question is in', VALUE_OPTIONAL),
'status' => new external_value(PARAM_RAW, 'current formatted state of the question', VALUE_OPTIONAL),
'mark' => new external_value(PARAM_RAW, 'the mark awarded', VALUE_OPTIONAL),
'maxmark' => new external_value(PARAM_FLOAT, 'the maximum mark possible for this question attempt', VALUE_OPTIONAL),
)
);
}
/**
* Return questions information for a given attempt.
*
* @param quiz_attempt $attemptobj the quiz attempt object
* @param bool $review whether if we are in review mode or not
* @param mixed $page string 'all' or integer page number
* @return array array of questions including data
*/
private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all') {
global $PAGE;
$questions = array();
$contextid = $attemptobj->get_quizobj()->get_context()->id;
$displayoptions = $attemptobj->get_display_options($review);
$renderer = $PAGE->get_renderer('mod_quiz');
foreach ($attemptobj->get_slots($page) as $slot) {
$question = array(
'slot' => $slot,
'type' => $attemptobj->get_question_type_name($slot),
'page' => $attemptobj->get_question_page($slot),
'flagged' => $attemptobj->is_question_flagged($slot),
'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code()
);
if ($attemptobj->is_real_question($slot)) {
$question['number'] = $attemptobj->get_question_number($slot);
$question['state'] = (string) $attemptobj->get_question_state($slot);
$question['status'] = $attemptobj->get_question_status($slot, $displayoptions->correctness);
}
if ($displayoptions->marks >= question_display_options::MAX_ONLY) {
$question['maxmark'] = $attemptobj->get_question_attempt($slot)->get_max_mark();
}
if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) {
$question['mark'] = $attemptobj->get_question_mark($slot);
}
$questions[] = $question;
}
return $questions;
}
/**
* Describes the parameters for get_attempt_data.
*
* @return external_external_function_parameters
* @since Moodle 3.1
*/
public static function get_attempt_data_parameters() {
return new external_function_parameters (
array(
'attemptid' => new external_value(PARAM_INT, 'attempt id'),
'page' => new external_value(PARAM_INT, 'page number'),
'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 information for the given attempt page for a quiz attempt in progress.
*
* @param int $attemptid attempt id
* @param int $page page number
* @param array $preflightdata preflight required data (like passwords)
* @return array of warnings and the attempt data, next page, message and questions
* @since Moodle 3.1
* @throws moodle_quiz_exceptions
*/
public static function get_attempt_data($attemptid, $page, $preflightdata = array()) {
$warnings = array();
$params = array(
'attemptid' => $attemptid,
'page' => $page,
'preflightdata' => $preflightdata,
);
$params = self::validate_parameters(self::get_attempt_data_parameters(), $params);
list($attemptobj, $messages) = self::validate_attempt($params);
if ($attemptobj->is_last_page($params['page'])) {
$nextpage = -1;
} else {
$nextpage = $params['page'] + 1;
}
$result = array();
$result['attempt'] = $attemptobj->get_attempt();
$result['messages'] = $messages;
$result['nextpage'] = $nextpage;
$result['warnings'] = $warnings;
$result['questions'] = self::get_attempt_questions_data($attemptobj, false, $params['page']);
return $result;
}
/**
* Describes the get_attempt_data return value.
*
* @return external_single_structure
* @since Moodle 3.1
*/
public static function get_attempt_data_returns() {
return new external_single_structure(
array(
'attempt' => self::attempt_structure(),
'messages' => new external_multiple_structure(
new external_value(PARAM_TEXT, 'access message'),
'access messages, will only be returned for users with mod/quiz:preview capability,
for other users this method will throw an exception if there are messages'),
'nextpage' => new external_value(PARAM_INT, 'next page number'),
'questions' => new external_multiple_structure(self::question_structure()),
'warnings' => new external_warnings(),
)
);
}
}

View File

@ -82,4 +82,13 @@ $functions = array(
'capabilities' => 'mod/quiz:attempt',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_quiz_get_attempt_data' => array(
'classname' => 'mod_quiz_external',
'methodname' => 'get_attempt_data',
'description' => 'Returns information for the given attempt page for a quiz attempt in progress.',
'type' => 'read',
'capabilities' => 'mod/quiz:attempt',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);

View File

@ -702,4 +702,21 @@ class mod_quiz_external_testcase extends externallib_advanced_testcase {
}
/**
* Test get_attempt_data
*/
public function test_get_attempt_data() {
$result = mod_quiz_external::get_attempt_data();
$result = external_api::clean_returnvalue(mod_quiz_external::get_attempt_data_returns(), $result);
try {
mod_quiz_external::get_attempt_data();
$this->fail('Exception expected due to missing capability.');
} catch (required_capability_exception $e) {
$this->assertEquals('nopermissions', $e->errorcode);
}
}
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2015111607;
$plugin->version = 2015111608;
$plugin->requires = 2015111000;
$plugin->component = 'mod_quiz';
$plugin->cron = 60;