Skip to content

Commit

Permalink
Merge branch 'MDL-52813-master' of git://github.com/jleyva/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
danpoltawski committed Mar 21, 2016
2 parents e372bb0 + 4c08658 commit 2569fac
Show file tree
Hide file tree
Showing 5 changed files with 526 additions and 68 deletions.
11 changes: 11 additions & 0 deletions mod/quiz/attemptlib.php
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,17 @@ public function get_question_action_time($slot) {
return $this->quba->get_question_action_time($slot);
}

/**
* Return the question type name for a given slot within the current attempt.
*
* @param int $slot the number used to identify this question within this attempt.
* @return string the question type name
* @since Moodle 3.1
*/
public function get_question_type_name($slot) {
return $this->quba->get_question($slot)->get_type_name();
}

/**
* Get the time remaining for an in-progress attempt, if the time is short
* enought that it would be worth showing a timer.
Expand Down
228 changes: 228 additions & 0 deletions mod/quiz/classes/external.php
Original file line number Diff line number Diff line change
Expand Up @@ -774,4 +774,232 @@ public static function start_attempt_returns() {
);
}

/**
* 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(),
)
);
}

}
9 changes: 9 additions & 0 deletions mod/quiz/db/services.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,13 @@
'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)
),
);
Loading

0 comments on commit 2569fac

Please sign in to comment.