Created
March 10, 2016 11:03
-
-
Save jleyva/7524826e88f12ee7a4c9 to your computer and use it in GitHub Desktop.
mod/quiz/external.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// This file is part of Moodle - http://moodle.org/ | |
// | |
// Moodle is free software: you can redistribute it and/or modify | |
// it under the terms of the GNU General Public License as published by | |
// the Free Software Foundation, either version 3 of the License, or | |
// (at your option) any later version. | |
// | |
// Moodle is distributed in the hope that it will be useful, | |
// but WITHOUT ANY WARRANTY; without even the implied warranty of | |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
// GNU General Public License for more details. | |
// | |
// You should have received a copy of the GNU General Public License | |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
/** | |
* Quiz external API | |
* | |
* @package mod_quiz | |
* @category external | |
* @copyright 2016 Juan Leyva <[email protected]> | |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
* @since Moodle 3.1 | |
*/ | |
defined('MOODLE_INTERNAL') || die; | |
require_once($CFG->libdir . '/externallib.php'); | |
require_once($CFG->dirroot . '/mod/quiz/locallib.php'); | |
/** | |
* Quiz external functions | |
* | |
* @package mod_quiz | |
* @category external | |
* @copyright 2016 Juan Leyva <[email protected]> | |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
* @since Moodle 3.1 | |
*/ | |
class mod_quiz_external extends external_api { | |
/** | |
* Describes the parameters for get_quizzes_by_courses. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_quizzes_by_courses_parameters() { | |
return new external_function_parameters ( | |
array( | |
'courseids' => new external_multiple_structure( | |
new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array() | |
), | |
) | |
); | |
} | |
/** | |
* Returns a list of quizzes in a provided list of courses, | |
* if no list is provided all quizzes that the user can view will be returned. | |
* | |
* @param array $courseids Array of course ids | |
* @return array of quizzes details | |
* @since Moodle 3.1 | |
*/ | |
public static function get_quizzes_by_courses($courseids = array()) { | |
global $USER; | |
$warnings = array(); | |
$returnedquizzes = array(); | |
$params = array( | |
'courseids' => $courseids, | |
); | |
$params = self::validate_parameters(self::get_quizzes_by_courses_parameters(), $params); | |
$mycourses = array(); | |
if (empty($params['courseids'])) { | |
$mycourses = enrol_get_my_courses(); | |
$params['courseids'] = array_keys($mycourses); | |
} | |
// Ensure there are courseids to loop through. | |
if (!empty($params['courseids'])) { | |
list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses); | |
// Get the quizzes in this course, this function checks users visibility permissions. | |
// We can avoid then additional validate_context calls. | |
$quizzes = get_all_instances_in_courses("quiz", $courses); | |
foreach ($quizzes as $quiz) { | |
$context = context_module::instance($quiz->coursemodule); | |
// Update quiz with override information. | |
$quiz = quiz_update_effective_access($quiz, $USER->id); | |
// Entry to return. | |
$quizdetails = array(); | |
// First, we return information that any user can see in the web interface. | |
$quizdetails['id'] = $quiz->id; | |
$quizdetails['coursemodule'] = $quiz->coursemodule; | |
$quizdetails['course'] = $quiz->course; | |
$quizdetails['name'] = external_format_string($quiz->name, $context->id); | |
if (has_capability('mod/quiz:view', $context)) { | |
// Format intro. | |
list($quizdetails['intro'], $quizdetails['introformat']) = external_format_text($quiz->intro, | |
$quiz->introformat, $context->id, 'mod_quiz', 'intro', null); | |
$viewablefields = array('timeopen', 'timeclose', 'grademethod', 'section', 'visible', 'groupmode', | |
'groupingid'); | |
$timenow = time(); | |
$quizobj = quiz::create($quiz->id, $USER->id); | |
$accessmanager = new quiz_access_manager($quizobj, $timenow, has_capability('mod/quiz:ignoretimelimits', | |
$context, null, false)); | |
// Fields the user could see if have access to the quiz. | |
if (!$accessmanager->prevent_access()) { | |
// Some times this function returns just empty. | |
$hasfeedback = quiz_has_feedback($quiz); | |
$quizdetails['hasfeedback'] = (!empty($hasfeedback)) ? 1 : 0; | |
$quizdetails['hasquestions'] = (int) $quizobj->has_questions(); | |
$quizdetails['autosaveperiod'] = get_config('quiz', 'autosaveperiod'); | |
$additionalfields = array('timelimit', 'attempts', 'attemptonlast', 'grademethod', 'decimalpoints', | |
'questiondecimalpoints', 'reviewattempt', 'reviewcorrectness', 'reviewmarks', | |
'reviewspecificfeedback', 'reviewgeneralfeedback', 'reviewrightanswer', | |
'reviewoverallfeedback', 'questionsperpage', 'navmethod', 'sumgrades', 'grade', | |
'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks', | |
'completionattemptsexhausted', 'completionpass', 'overduehandling', | |
'graceperiod', 'preferredbehaviour', 'canredoquestions'); | |
$viewablefields = array_merge($viewablefields, $additionalfields); | |
} | |
// Fields only for managers. | |
if (has_capability('moodle/course:manageactivities', $context)) { | |
$additionalfields = array('shuffleanswers', 'timecreated', 'timemodified', 'password', 'subnet'); | |
$viewablefields = array_merge($viewablefields, $additionalfields); | |
} | |
foreach ($viewablefields as $field) { | |
$quizdetails[$field] = $quiz->{$field}; | |
} | |
} | |
$returnedquizzes[] = $quizdetails; | |
} | |
} | |
$result = array(); | |
$result['quizzes'] = $returnedquizzes; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_quizzes_by_courses return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_quizzes_by_courses_returns() { | |
return new external_single_structure( | |
array( | |
'quizzes' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'id' => new external_value(PARAM_INT, 'Standard Moodle primary key.'), | |
'course' => new external_value(PARAM_INT, 'Foreign key reference to the course this quiz is part of.'), | |
'coursemodule' => new external_value(PARAM_INT, 'Course module id.'), | |
'name' => new external_value(PARAM_RAW, 'Quiz name.'), | |
'intro' => new external_value(PARAM_RAW, 'Quiz introduction text.', VALUE_OPTIONAL), | |
'introformat' => new external_format_value('intro', VALUE_OPTIONAL), | |
'timeopen' => new external_value(PARAM_INT, 'The time when this quiz opens. (0 = no restriction.)', | |
VALUE_OPTIONAL), | |
'timeclose' => new external_value(PARAM_INT, 'The time when this quiz closes. (0 = no restriction.)', | |
VALUE_OPTIONAL), | |
'timelimit' => new external_value(PARAM_INT, 'The time limit for quiz attempts, in seconds.', | |
VALUE_OPTIONAL), | |
'overduehandling' => new external_value(PARAM_ALPHA, 'The method used to handle overdue attempts. | |
\'autosubmit\', \'graceperiod\' or \'autoabandon\'.', | |
VALUE_OPTIONAL), | |
'graceperiod' => new external_value(PARAM_INT, 'The amount of time (in seconds) after the time limit | |
runs out during which attempts can still be submitted, | |
if overduehandling is set to allow it.', VALUE_OPTIONAL), | |
'preferredbehaviour' => new external_value(PARAM_ALPHANUMEXT, 'The behaviour to ask questions to use.', | |
VALUE_OPTIONAL), | |
'canredoquestions' => new external_value(PARAM_INT, 'Allows students to redo any completed question | |
within a quiz attempt.', VALUE_OPTIONAL), | |
'attempts' => new external_value(PARAM_INT, 'The maximum number of attempts a student is allowed.', | |
VALUE_OPTIONAL), | |
'attemptonlast' => new external_value(PARAM_INT, 'Whether subsequent attempts start from teh answer | |
to the previous attempt (1) or start blank (0).', | |
VALUE_OPTIONAL), | |
'grademethod' => new external_value(PARAM_INT, 'One of the values QUIZ_GRADEHIGHEST, QUIZ_GRADEAVERAGE, | |
QUIZ_ATTEMPTFIRST or QUIZ_ATTEMPTLAST.', VALUE_OPTIONAL), | |
'decimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when displaying | |
grades.', VALUE_OPTIONAL), | |
'questiondecimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when | |
displaying question grades. | |
(-1 means use decimalpoints.)', VALUE_OPTIONAL), | |
'reviewattempt' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz | |
attempts at various times. This is a bit field, decoded by the | |
mod_quiz_display_options class. It is formed by ORing together | |
the constants defined there.', VALUE_OPTIONAL), | |
'reviewcorrectness' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz | |
attempts at various times. | |
A bit field, like reviewattempt.', VALUE_OPTIONAL), | |
'reviewmarks' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz attempts | |
at various times. A bit field, like reviewattempt.', | |
VALUE_OPTIONAL), | |
'reviewspecificfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their | |
quiz attempts at various times. A bit field, like | |
reviewattempt.', VALUE_OPTIONAL), | |
'reviewgeneralfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their | |
quiz attempts at various times. A bit field, like | |
reviewattempt.', VALUE_OPTIONAL), | |
'reviewrightanswer' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz | |
attempts at various times. A bit field, like | |
reviewattempt.', VALUE_OPTIONAL), | |
'reviewoverallfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz | |
attempts at various times. A bit field, like | |
reviewattempt.', VALUE_OPTIONAL), | |
'questionsperpage' => new external_value(PARAM_INT, 'How often to insert a page break when editing | |
the quiz, or when shuffling the question order.', | |
VALUE_OPTIONAL), | |
'navmethod' => new external_value(PARAM_ALPHA, 'Any constraints on how the user is allowed to navigate | |
around the quiz. Currently recognised values are | |
\'free\' and \'seq\'.', VALUE_OPTIONAL), | |
'shuffleanswers' => new external_value(PARAM_INT, 'Whether the parts of the question should be shuffled, | |
in those question types that support it.', VALUE_OPTIONAL), | |
'sumgrades' => new external_value(PARAM_FLOAT, 'The total of all the question instance maxmarks.', | |
VALUE_OPTIONAL), | |
'grade' => new external_value(PARAM_FLOAT, 'The total that the quiz overall grade is scaled to be | |
out of.', VALUE_OPTIONAL), | |
'timecreated' => new external_value(PARAM_INT, 'The time when the quiz was added to the course.', | |
VALUE_OPTIONAL), | |
'timemodified' => new external_value(PARAM_INT, 'Last modified time.', | |
VALUE_OPTIONAL), | |
'password' => new external_value(PARAM_RAW, 'A password that the student must enter before starting or | |
continuing a quiz attempt.', VALUE_OPTIONAL), | |
'subnet' => new external_value(PARAM_RAW, 'Used to restrict the IP addresses from which this quiz can | |
be attempted. The format is as requried by the address_in_subnet | |
function.', VALUE_OPTIONAL), | |
'browsersecurity' => new external_value(PARAM_ALPHANUMEXT, 'Restriciton on the browser the student must | |
use. E.g. \'securewindow\'.', VALUE_OPTIONAL), | |
'delay1' => new external_value(PARAM_INT, 'Delay that must be left between the first and second attempt, | |
in seconds.', VALUE_OPTIONAL), | |
'delay2' => new external_value(PARAM_INT, 'Delay that must be left between the second and subsequent | |
attempt, in seconds.', VALUE_OPTIONAL), | |
'showuserpicture' => new external_value(PARAM_INT, 'Option to show the user\'s picture during the | |
attempt and on the review page.', VALUE_OPTIONAL), | |
'showblocks' => new external_value(PARAM_INT, 'Whether blocks should be shown on the attempt.php and | |
review.php pages.', VALUE_OPTIONAL), | |
'completionattemptsexhausted' => new external_value(PARAM_INT, 'Mark quiz complete when the student has | |
exhausted the maximum number of attempts', | |
VALUE_OPTIONAL), | |
'completionpass' => new external_value(PARAM_INT, 'Wheter to require passing grade', VALUE_OPTIONAL), | |
'autosaveperiod' => new external_value(PARAM_INT, 'Auto-save delay', VALUE_OPTIONAL), | |
'hasfeedback' => new external_value(PARAM_INT, 'Whether the quiz has any non-blank feedback text', | |
VALUE_OPTIONAL), | |
'hasquestions' => new external_value(PARAM_INT, 'Whether the quiz has questions', VALUE_OPTIONAL), | |
'section' => new external_value(PARAM_INT, 'Course section id', VALUE_OPTIONAL), | |
'visible' => new external_value(PARAM_INT, 'Module visibility', VALUE_OPTIONAL), | |
'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL), | |
'groupingid' => new external_value(PARAM_INT, 'Grouping id', VALUE_OPTIONAL), | |
) | |
) | |
), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for view_quiz. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function view_quiz_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
) | |
); | |
} | |
/** | |
* Trigger the course module viewed event and update the module completion status. | |
* | |
* @param int $quizid quiz instance id | |
* @return array of warnings and status result | |
* @since Moodle 3.1 | |
* @throws moodle_exception | |
*/ | |
public static function view_quiz($quizid) { | |
global $DB; | |
$params = self::validate_parameters(self::view_quiz_parameters(), array('quizid' => $quizid)); | |
$warnings = array(); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
// Trigger course_module_viewed event and completion. | |
quiz_view($quiz, $course, $cm, $context); | |
$result = array(); | |
$result['status'] = true; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the view_quiz return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function view_quiz_returns() { | |
return new external_single_structure( | |
array( | |
'status' => new external_value(PARAM_BOOL, 'status: true if success'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for get_user_attempts. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_user_attempts_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
'userid' => new external_value(PARAM_INT, 'user id, empty for current user', VALUE_DEFAULT, 0), | |
'status' => new external_value(PARAM_ALPHA, 'quiz status: all, finished or unfinished', VALUE_DEFAULT, 'finished'), | |
'includepreviews' => new external_value(PARAM_BOOL, 'whether to include previews or not', VALUE_DEFAULT, false), | |
) | |
); | |
} | |
/** | |
* Return a list of attempts for the given quiz and user. | |
* | |
* @param int $quizid quiz instance id | |
* @param int $userid user id | |
* @param string $status quiz status: all, finished or unfinished | |
* @param bool $includepreviews whether to include previews or not | |
* @return array of warnings and the list of attempts | |
* @since Moodle 3.1 | |
* @throws invalid_parameter_exception | |
*/ | |
public static function get_user_attempts($quizid, $userid = 0, $status = 'finished', $includepreviews = false) { | |
global $DB, $USER; | |
$warnings = array(); | |
$params = array( | |
'quizid' => $quizid, | |
'userid' => $userid, | |
'status' => $status, | |
'includepreviews' => $includepreviews, | |
); | |
$params = self::validate_parameters(self::get_user_attempts_parameters(), $params); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
if (!in_array($params['status'], array('all', 'finished', 'unfinished'))) { | |
throw new invalid_parameter_exception('Invalid status value'); | |
} | |
// Default value for userid. | |
if (empty($params['userid'])) { | |
$params['userid'] = $USER->id; | |
} | |
$user = core_user::get_user($params['userid'], '*', MUST_EXIST); | |
core_user::require_active_user($user); | |
// Extra checks so only users with permissions can view other users attempts. | |
if ($USER->id != $user->id) { | |
require_capability('mod/quiz:viewreports', $context); | |
} | |
$attempts = quiz_get_user_attempts($quiz->id, $user->id, $params['status'], $params['includepreviews']); | |
$result = array(); | |
$result['attempts'] = $attempts; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes a single attempt structure. | |
* | |
* @return external_single_structure the attempt structure | |
*/ | |
private static function attempt_structure() { | |
return new external_single_structure( | |
array( | |
'id' => new external_value(PARAM_INT, 'Attempt id.', VALUE_OPTIONAL), | |
'quiz' => new external_value(PARAM_INT, 'Foreign key reference to the quiz that was attempted.', | |
VALUE_OPTIONAL), | |
'userid' => new external_value(PARAM_INT, 'Foreign key reference to the user whose attempt this is.', | |
VALUE_OPTIONAL), | |
'attempt' => new external_value(PARAM_INT, 'Sequentially numbers this students attempts at this quiz.', | |
VALUE_OPTIONAL), | |
'uniqueid' => new external_value(PARAM_INT, 'Foreign key reference to the question_usage that holds the | |
details of the the question_attempts that make up this quiz | |
attempt.', VALUE_OPTIONAL), | |
'layout' => new external_value(PARAM_RAW, 'Attempt layout.', VALUE_OPTIONAL), | |
'currentpage' => new external_value(PARAM_INT, 'Attempt current page.', VALUE_OPTIONAL), | |
'preview' => new external_value(PARAM_INT, 'Whether is a preview attempt or not.', VALUE_OPTIONAL), | |
'state' => new external_value(PARAM_ALPHA, 'The current state of the attempts. \'inprogress\', | |
\'overdue\', \'finished\' or \'abandoned\'.', VALUE_OPTIONAL), | |
'timestart' => new external_value(PARAM_INT, 'Time when the attempt was started.', VALUE_OPTIONAL), | |
'timefinish' => new external_value(PARAM_INT, 'Time when the attempt was submitted. | |
0 if the attempt has not been submitted yet.', VALUE_OPTIONAL), | |
'timemodified' => new external_value(PARAM_INT, 'Last modified time.', VALUE_OPTIONAL), | |
'timecheckstate' => new external_value(PARAM_INT, 'Next time quiz cron should check attempt for | |
state changes. NULL means never check.', VALUE_OPTIONAL), | |
'sumgrades' => new external_value(PARAM_FLOAT, 'Total marks for this attempt.', VALUE_OPTIONAL), | |
) | |
); | |
} | |
/** | |
* Describes the get_user_attempts return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_user_attempts_returns() { | |
return new external_single_structure( | |
array( | |
'attempts' => new external_multiple_structure(self::attempt_structure()), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for get_access_information. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_access_information_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
'attemptid' => new external_value(PARAM_INT, 'attempt id, 0 for the user last attempt if exists', VALUE_DEFAULT, 0), | |
) | |
); | |
} | |
/** | |
* Return access information for a given quiz. | |
* | |
* @param int $quizid quiz instance id | |
* @param int $attemptid attempt id, 0 for the user last attempt if exists | |
* @return array of warnings and the access information | |
* @since Moodle 3.1 | |
* @throws moodle_quiz_exception | |
*/ | |
public static function get_access_information($quizid, $attemptid = 0) { | |
global $DB, $USER, $CFG; | |
$warnings = array(); | |
$params = array( | |
'quizid' => $quizid, | |
'attemptid' => $attemptid, | |
); | |
$params = self::validate_parameters(self::get_access_information_parameters(), $params); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
$attempttocheck = 0; | |
if (!empty($params['attemptid'])) { | |
$attemptobj = quiz_attempt::create($params['attemptid']); | |
if ($attemptobj->get_userid() != $USER->id) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); | |
} | |
$attempttocheck = $attemptobj->get_attempt(); | |
} | |
$result = array(); | |
// Capabilities first. | |
$result['canattempt'] = has_capability('mod/quiz:attempt', $context);; | |
$result['canmanage'] = has_capability('mod/quiz:manage', $context);; | |
$result['canpreview'] = has_capability('mod/quiz:preview', $context);; | |
$result['canreviewmyattempts'] = has_capability('mod/quiz:reviewmyattempts', $context);; | |
$result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);; | |
// Access manager now. | |
$quizobj = quiz::create($cm->instance, $USER->id); | |
$ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); | |
$timenow = time(); | |
$accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); | |
$attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true); | |
$lastfinishedattempt = end($attempts); | |
if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { | |
$attempts[] = $unfinishedattempt; | |
// Check if the attempt is now overdue. In that case the state will change. | |
$quizobj->create_attempt_object($unfinishedattempt)->handle_if_time_expired(time(), false); | |
if ($unfinishedattempt->state != quiz_attempt::IN_PROGRESS and $unfinishedattempt->state != quiz_attempt::OVERDUE) { | |
$lastfinishedattempt = $unfinishedattempt; | |
} | |
} | |
$numattempts = count($attempts); | |
if (!$attempttocheck) { | |
$attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt; | |
} | |
$result['isfinished'] = $accessmanager->is_finished($numattempts, $lastfinishedattempt); | |
$result['accessrules'] = $accessmanager->describe_rules(); | |
$result['activerulenames'] = $accessmanager->get_active_rule_names(); | |
$result['preventaccessreasons'] = $accessmanager->prevent_access(); | |
$result['preventnewattemptreasons'] = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt); | |
if ($attempttocheck) { | |
$endtime = $accessmanager->get_end_time($attempttocheck); | |
$result['endtime'] = ($endtime === false) ? 0 : $endtime; | |
$result['ispreflightcheckrequired'] = $accessmanager->is_preflight_check_required($unfinishedattempt ? $unfinishedattempt->id : null); | |
} | |
// Question types used. | |
$result['questiontypes'] = array(); | |
$result['plainhtmlqtypes'] = array(); | |
$quizobj->preload_questions(); | |
$quizobj->load_questions(); | |
// To control if we need to look in categories for questions. | |
$qcategories = array(); | |
// We must be careful with random questions, if we find a random question we must assume that the quiz may content | |
// any of the questions in the referenced category (or subcategories). | |
foreach ($quizobj->get_questions() as $questiondata) { | |
if ($questiondata->qtype == 'random') { | |
$includesubcategories = (bool) $questiondata->questiontext; | |
if (!isset($qcategories[$questiondata->category])) { | |
$qcategories[$questiondata->category] = false; | |
} | |
if ($includesubcategories) { | |
$qcategories[$questiondata->category] = true; | |
} | |
} else { | |
if (!in_array($questiondata->qtype, $result['questiontypes'])) { | |
$qtype = question_bank::get_qtype($questiondata->qtype); | |
if ($qtype->is_plain_html()) { | |
$result['plainhtmlqtypes'][] = $questiondata->qtype; | |
} | |
$result['questiontypes'][] = $questiondata->qtype; | |
} | |
} | |
} | |
if (!empty($qcategories)) { | |
require_once($CFG->libdir . '/questionlib.php'); | |
// We have to look for all the question types in these categories. | |
$categoriestolook = array(); | |
foreach ($qcategories as $cat => $includesubcats) { | |
if ($includesubcats) { | |
$categoriestolook = array_merge($categoriestolook, question_categorylist($cat)); | |
} else { | |
$categoriestolook[] = $cat; | |
} | |
} | |
$result['questiontypes'] = array_merge($result['questiontypes'], question_get_qtypes_in_categories($categoriestolook)); | |
} | |
$result['questiontypes'] = array_unique($result['questiontypes']); | |
asort($result['questiontypes']); | |
$result['plainhtmlqtypes'] = array_unique($result['plainhtmlqtypes']); | |
asort($result['plainhtmlqtypes']); | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_access_information return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_access_information_returns() { | |
return new external_single_structure( | |
array( | |
'canattempt' => new external_value(PARAM_BOOL, 'Whether the user can do the quiz or not.'), | |
'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can edit the quiz settings or not.'), | |
'canpreview' => new external_value(PARAM_BOOL, 'Whether the user can preview the quiz or not.'), | |
'canreviewmyattempts' => new external_value(PARAM_BOOL, 'Whether the users can review their previous attempts | |
or not.'), | |
'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the quiz reports or not.'), | |
'endtime' => new external_value(PARAM_INT, 'When the attempt must be submitted (determined by rules).', | |
VALUE_OPTIONAL), | |
'isfinished' => new external_value(PARAM_BOOL, 'Whether there is no way the user will ever be allowed to attempt.'), | |
'ispreflightcheckrequired' => new external_value(PARAM_BOOL, 'whether a check is required before the user | |
starts/continues his attempt.', VALUE_OPTIONAL), | |
'accessrules' => new external_multiple_structure( | |
new external_value(PARAM_TEXT, 'rule description'), 'list of rules'), | |
'activerulenames' => new external_multiple_structure( | |
new external_value(PARAM_PLUGIN, 'rule plugin names'), 'list of active rules'), | |
'preventaccessreasons' => new external_multiple_structure( | |
new external_value(PARAM_TEXT, 'access restriction description'), 'list of reasons'), | |
'preventnewattemptreasons' => new external_multiple_structure( | |
new external_value(PARAM_TEXT, 'access restriction description'), | |
'list of reasons'), | |
'questiontypes' => new external_multiple_structure( | |
new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'), | |
'plainhtmlqtypes' => new external_multiple_structure( | |
new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for get_user_best_grade. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_user_best_grade_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
'userid' => new external_value(PARAM_INT, 'user id', VALUE_DEFAULT, 0), | |
) | |
); | |
} | |
/** | |
* Get the best current grade for the given user on a quiz. | |
* | |
* @param int $quizid quiz instance id | |
* @param int $userid user id | |
* @return array of warnings and the grade information | |
* @since Moodle 3.1 | |
*/ | |
public static function get_user_best_grade($quizid, $userid = 0) { | |
global $DB, $USER; | |
$warnings = array(); | |
$params = array( | |
'quizid' => $quizid, | |
'userid' => $userid, | |
); | |
$params = self::validate_parameters(self::get_user_best_grade_parameters(), $params); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
// Default value for userid. | |
if (empty($params['userid'])) { | |
$params['userid'] = $USER->id; | |
} | |
$user = core_user::get_user($params['userid'], '*', MUST_EXIST); | |
core_user::require_active_user($user); | |
// Extra checks so only users with permissions can view other users attempts. | |
if ($USER->id != $user->id) { | |
require_capability('mod/quiz:viewreports', $context); | |
} | |
$result = array(); | |
$grade = quiz_get_best_grade($quiz, $user->id); | |
if ($grade === null) { | |
$result['hasgrade'] = false; | |
} else { | |
$result['hasgrade'] = true; | |
$result['grade'] = $grade; | |
} | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_user_best_grade return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_user_best_grade_returns() { | |
return new external_single_structure( | |
array( | |
'hasgrade' => new external_value(PARAM_BOOL, 'Whether the user has a grade on the given quiz.'), | |
'grade' => new external_value(PARAM_FLOAT, 'The grade (only if the user has a grade).', VALUE_OPTIONAL), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for get_combined_review_options. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_combined_review_options_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0), | |
) | |
); | |
} | |
/** | |
* Combines the review options from a number of different quiz attempts. | |
* | |
* @param int $quizid quiz instance id | |
* @param int $userid user id (empty for current user) | |
* @return array of warnings and the review options | |
* @since Moodle 3.1 | |
*/ | |
public static function get_combined_review_options($quizid, $userid = 0) { | |
global $DB, $USER; | |
$warnings = array(); | |
$params = array( | |
'quizid' => $quizid, | |
'userid' => $userid, | |
); | |
$params = self::validate_parameters(self::get_combined_review_options_parameters(), $params); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
// Default value for userid. | |
if (empty($params['userid'])) { | |
$params['userid'] = $USER->id; | |
} | |
$user = core_user::get_user($params['userid'], '*', MUST_EXIST); | |
core_user::require_active_user($user); | |
// Extra checks so only users with permissions can view other users attempts. | |
if ($USER->id != $user->id) { | |
require_capability('mod/quiz:viewreports', $context); | |
} | |
$attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all', true); | |
$result = array(); | |
$result['someoptions'] = []; | |
$result['alloptions'] = []; | |
list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts); | |
foreach (array('someoptions', 'alloptions') as $typeofoption) { | |
foreach ($$typeofoption as $key => $value) { | |
$result[$typeofoption][] = array( | |
"name" => $key, | |
"value" => (!empty($value)) ? $value : 0 | |
); | |
} | |
} | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_combined_review_options return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_combined_review_options_returns() { | |
return new external_single_structure( | |
array( | |
'someoptions' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), | |
'value' => new external_value(PARAM_INT, 'option value'), | |
) | |
) | |
), | |
'alloptions' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), | |
'value' => new external_value(PARAM_INT, 'option value'), | |
) | |
) | |
), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for start_attempt. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function start_attempt_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance 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() | |
), | |
'forcenew' => new external_value(PARAM_BOOL, 'Whether to force a new attempt or not.', VALUE_DEFAULT, false), | |
) | |
); | |
} | |
/** | |
* Starts a new attempt at a quiz. | |
* | |
* @param int $quizid quiz instance id | |
* @param array $preflightdata preflight required data (like passwords) | |
* @param bool $forcenew Whether to force a new attempt or not. | |
* @return array of warnings and the attempt basic data | |
* @since Moodle 3.1 | |
* @throws moodle_quiz_exception | |
*/ | |
public static function start_attempt($quizid, $preflightdata = array(), $forcenew = false) { | |
global $DB, $USER; | |
$warnings = array(); | |
$attempt = array(); | |
$params = array( | |
'quizid' => $quizid, | |
'preflightdata' => $preflightdata, | |
'forcenew' => $forcenew, | |
); | |
$params = self::validate_parameters(self::start_attempt_parameters(), $params); | |
$forcenew = $params['forcenew']; | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
$quizobj = quiz::create($cm->instance, $USER->id); | |
// Check questions. | |
if (!$quizobj->has_questions()) { | |
throw new moodle_quiz_exception($quizobj, 'noquestionsfound'); | |
} | |
// Create an object to manage all the other (non-roles) access rules. | |
$timenow = time(); | |
$accessmanager = $quizobj->get_access_manager($timenow); | |
// Validate permissions for creating a new attempt and start a new preview attempt if required. | |
list($currentattemptid, $attemptnumber, $lastattempt, $messages, $page) = quiz_validate_new_attempt($quizobj, | |
$accessmanager, $forcenew, -1, false); | |
// Check access. | |
if (!$quizobj->is_preview_user() && $messages) { | |
// Create warnings with the exact messages. | |
foreach ($messages as $message) { | |
$warnings[] = array( | |
'item' => 'quiz', | |
'itemid' => $quiz->id, | |
'warningcode' => '1', | |
'message' => clean_text($message, PARAM_TEXT) | |
); | |
} | |
} else { | |
if ($accessmanager->is_preflight_check_required($currentattemptid)) { | |
// Need to do some checks before allowing the user to continue. | |
$provideddata = array(); | |
foreach ($params['preflightdata'] as $data) { | |
$provideddata[$data['name']] = $data['value']; | |
} | |
$errors = $accessmanager->run_preflight_checks($provideddata, [], $currentattemptid); | |
if (!empty($errors)) { | |
throw new moodle_quiz_exception($quizobj, array_shift($errors)); | |
} | |
// Pre-flight check passed. | |
$accessmanager->notify_preflight_check_passed($currentattemptid); | |
} | |
if ($currentattemptid) { | |
if ($lastattempt->state == quiz_attempt::OVERDUE) { | |
throw new moodle_quiz_exception($quizobj, 'stateoverdue'); | |
} else { | |
throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress'); | |
} | |
} | |
$attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt); | |
} | |
$result = array(); | |
$result['attempt'] = $attempt; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the start_attempt return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function start_attempt_returns() { | |
return new external_single_structure( | |
array( | |
'attempt' => self::attempt_structure(), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Utility function for validating a given attempt | |
* | |
* @param array $params array of parameters including the attemptid and preflight data | |
* @param bool $checkaccessrules whether to check the quiz access rules or not | |
* @param bool $failifoverdue whether to return error if the attempt is overdue | |
* @return array containing the attempt object and access messages | |
* @throws moodle_quiz_exception | |
* @since Moodle 3.1 | |
*/ | |
protected static function validate_attempt($params, $checkaccessrules = true, $failifoverdue = true) { | |
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'); | |
} | |
// Check the access rules. | |
$accessmanager = $attemptobj->get_access_manager(time()); | |
$messages = array(); | |
if ($checkaccessrules) { | |
// If the attempt is now overdue, or abandoned, deal with that. | |
$attemptobj->handle_if_time_expired(time(), true); | |
$messages = $accessmanager->prevent_access(); | |
if (!$ispreviewuser && $messages) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror'); | |
} | |
} | |
// Attempt closed?. | |
if ($attemptobj->is_finished()) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed'); | |
} else if ($failifoverdue && $attemptobj->get_state() == quiz_attempt::OVERDUE) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue'); | |
} | |
// 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->run_preflight_checks($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']); | |
} | |
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(), 'invalidpagenumber'); | |
} | |
// Prevent out of sequence access. | |
quiz_check_page_access($attemptobj, $params['page'], false); | |
// 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'), | |
'name' => new external_value(PARAM_RAW, 'question name'), | |
'type' => new external_value(PARAM_PLUGIN, 'question type'), | |
'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'), | |
'html' => new external_value(PARAM_RAW, 'the question rendered'), | |
'number' => new external_value(PARAM_INT, 'question sequencial number', VALUE_OPTIONAL), | |
'state' => new external_value(PARAM_ALPHA, 'the state id where the question is in', VALUE_OPTIONAL), | |
'status' => new external_value(PARAM_RAW, 'current status of the question', VALUE_OPTIONAL), | |
'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not', VALUE_OPTIONAL), | |
'mark' => new external_value(PARAM_RAW, 'the mark awarded', 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 | |
* @param bool $includemark whether to include marks or not | |
* @return array array of questions including data | |
*/ | |
private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all', $includemark = false) { | |
global $PAGE; | |
$questions = array(); | |
$contextid = $attemptobj->get_quizobj()->get_context()->id; | |
$displayoptions = $attemptobj->get_display_options(true); | |
$renderer = $PAGE->get_renderer('mod_quiz'); | |
foreach ($attemptobj->get_slots($page) as $slot) { | |
$question = array( | |
'slot' => $slot, | |
'name' => external_format_string($attemptobj->get_question_name($slot), $contextid), | |
'type' => $attemptobj->get_question_type_class_name($slot), | |
'page' => $attemptobj->get_question_page($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); | |
$question['flagged'] = $attemptobj->is_question_flagged($slot); | |
} | |
if ($includemark) { | |
$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'], true); | |
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'), | |
'nextpage' => new external_value(PARAM_INT, 'next page number'), | |
'questions' => new external_multiple_structure(self::question_structure()), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* 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, true, false); | |
$displayoptions = $attemptobj->get_display_options(false); | |
$marksdata = $displayoptions->marks >= question_display_options::MARK_AND_MAX; | |
$result = array(); | |
$result['warnings'] = $warnings; | |
$result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all', $marksdata); | |
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(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for save_attempt. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function save_attempt_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
'data' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'name' => new external_value(PARAM_RAW, 'data name'), | |
'value' => new external_value(PARAM_RAW, 'data value'), | |
) | |
), 'the data to be saved' | |
), | |
'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() | |
) | |
) | |
); | |
} | |
/** | |
* Processes save requests during the quiz. This function is intended for the quiz auto-save feature. | |
* | |
* @param int $attemptid attempt id | |
* @param array $data the data to be saved | |
* @param array $preflightdata preflight required data (like passwords) | |
* @return array of warnings and execution result | |
* @since Moodle 3.1 | |
*/ | |
public static function save_attempt($attemptid, $data, $preflightdata = array()) { | |
global $DB; | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
'data' => $data, | |
'preflightdata' => $preflightdata, | |
); | |
$params = self::validate_parameters(self::save_attempt_parameters(), $params); | |
// Add a page, required by validate_attempt. | |
list($attemptobj, $messages) = self::validate_attempt($params); | |
$transaction = $DB->start_delegated_transaction(); | |
// Create the $_POST object required by the question engine. | |
$_POST = array(); | |
foreach ($data as $element) { | |
$_POST[$element['name']] = $element['value']; | |
} | |
$timenow = time(); | |
$attemptobj->process_auto_save($timenow); | |
$transaction->allow_commit(); | |
$result = array(); | |
$result['status'] = true; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the save_attempt return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function save_attempt_returns() { | |
return new external_single_structure( | |
array( | |
'status' => new external_value(PARAM_BOOL, 'status: true if success'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for process_attempt. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function process_attempt_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
'data' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'name' => new external_value(PARAM_RAW, 'data name'), | |
'value' => new external_value(PARAM_RAW, 'data value'), | |
) | |
), | |
'the data to be saved' | |
), | |
'finishattempt' => new external_value(PARAM_BOOL, 'whether to finish or not the attempt', VALUE_DEFAULT, false), | |
'timeup' => new external_value(PARAM_BOOL, 'whether the WS was called by a timer when the time is up', | |
VALUE_DEFAULT, false), | |
'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() | |
) | |
) | |
); | |
} | |
/** | |
* Process responses during an attempt at a quiz and also deals with attempts finishing. | |
* | |
* @param int $attemptid attempt id | |
* @param array $data the data to be saved | |
* @param bool $finishattempt whether to finish or not the attempt | |
* @param bool $timeup whether the WS was called by a timer when the time is up | |
* @param array $preflightdata preflight required data (like passwords) | |
* @return array of warnings and the attempt state after the processing | |
* @since Moodle 3.1 | |
*/ | |
public static function process_attempt($attemptid, $data, $finishattempt = false, $timeup = false, $preflightdata = array()) { | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
'data' => $data, | |
'finishattempt' => $finishattempt, | |
'timeup' => $timeup, | |
'preflightdata' => $preflightdata, | |
); | |
$params = self::validate_parameters(self::process_attempt_parameters(), $params); | |
// Do not check access manager rules. | |
list($attemptobj, $messages) = self::validate_attempt($params, false); | |
// Create the $_POST object required by the question engine. | |
$_POST = array(); | |
foreach ($params['data'] as $element) { | |
$_POST[$element['name']] = $element['value']; | |
} | |
$timenow = time(); | |
$finishattempt = $params['finishattempt']; | |
$timeup = $params['timeup']; | |
$result = array(); | |
$result['state'] = quiz_process_attempt($attemptobj, $timenow, $finishattempt, $timeup, 0); | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the process_attempt return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function process_attempt_returns() { | |
return new external_single_structure( | |
array( | |
'state' => new external_value(PARAM_ALPHANUMEXT, 'state: the new attempt state: | |
inprogress, finished, overdue, abandoned'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Validate an attempt finished for review. The attempt would be reviewed by a user or a teacher. | |
* | |
* @param array $params Array of parameters including the attemptid | |
* @return array containing the attempt object and display options | |
* @since Moodle 3.1 | |
* @throws moodle_exception | |
* @throws moodle_quiz_exception | |
*/ | |
protected static function validate_attempt_review($params) { | |
$attemptobj = quiz_attempt::create($params['attemptid']); | |
$attemptobj->check_review_capability(); | |
$displayoptions = $attemptobj->get_display_options(true); | |
if ($attemptobj->is_own_attempt()) { | |
if (!$attemptobj->is_finished()) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed'); | |
} else if (!$displayoptions->attempt) { | |
throw new moodle_exception($attemptobj->cannot_review_message()); | |
} | |
} else if (!$attemptobj->is_review_allowed()) { | |
throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt'); | |
} | |
return array($attemptobj, $displayoptions); | |
} | |
/** | |
* Describes the parameters for get_attempt_review. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_attempt_review_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
'page' => new external_value(PARAM_INT, 'page number, empty for all the questions in all the pages', | |
VALUE_DEFAULT, -1), | |
) | |
); | |
} | |
/** | |
* Returns review information for the given finished attempt, can be used by users or teachers. | |
* | |
* @param int $attemptid attempt id | |
* @param int $page page number, empty for all the questions in all the pages | |
* @return array of warnings and the attempt data, feedback and questions | |
* @since Moodle 3.1 | |
* @throws moodle_exception | |
* @throws moodle_quiz_exception | |
*/ | |
public static function get_attempt_review($attemptid, $page = -1) { | |
global $PAGE; | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
'page' => $page, | |
); | |
$params = self::validate_parameters(self::get_attempt_review_parameters(), $params); | |
list($attemptobj, $displayoptions) = self::validate_attempt_review($params); | |
if ($params['page'] !== -1) { | |
$page = $attemptobj->force_page_number_into_range($params['page']); | |
} else { | |
$page = 'all'; | |
} | |
// Prepare the output. | |
$result = array(); | |
$result['attempt'] = $attemptobj->get_attempt(); | |
$result['questions'] = self::get_attempt_questions_data($attemptobj, true, $page, true); | |
$result['additionaldata'] = array(); | |
// Summary data (from behaviours). | |
$summarydata = $attemptobj->get_additional_summary_data($displayoptions); | |
foreach ($summarydata as $key => $data) { | |
// This text does not need formatting (no need for external_format_[string|text]). | |
$result['additionaldata'][] = array( | |
'id' => $key, | |
'title' => $data['title'], $attemptobj->get_quizobj()->get_context()->id, | |
'content' => $data['content'], | |
); | |
} | |
// Feedback if there is any, and the user is allowed to see it now. | |
$grade = quiz_rescale_grade($attemptobj->get_attempt()->sumgrades, $attemptobj->get_quiz(), false); | |
$feedback = $attemptobj->get_overall_feedback($grade); | |
if ($displayoptions->overallfeedback && $feedback) { | |
$result['additionaldata'][] = array( | |
'id' => 'feedback', | |
'title' => get_string('feedback', 'quiz'), | |
'content' => $feedback, | |
); | |
} | |
$result['rescaledgrade'] = $grade; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_attempt_review return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_attempt_review_returns() { | |
return new external_single_structure( | |
array( | |
'rescaledgrade' => new external_value(PARAM_RAW, 'rescaled grade for the quiz (or empty or "notyetgraded")'), | |
'attempt' => self::attempt_structure(), | |
'additionaldata' => new external_multiple_structure( | |
new external_single_structure( | |
array( | |
'id' => new external_value(PARAM_ALPHANUMEXT, 'id of the data'), | |
'title' => new external_value(PARAM_TEXT, 'data title'), | |
'content' => new external_value(PARAM_RAW, 'data content'), | |
) | |
) | |
), | |
'questions' => new external_multiple_structure(self::question_structure()), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for view_attempt. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
'page' => new external_value(PARAM_INT, 'page number'), | |
) | |
); | |
} | |
/** | |
* Trigger the attempt viewed event. | |
* | |
* @param int $attemptid attempt id | |
* @param int $page page number | |
* @return array of warnings and status result | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt($attemptid, $page) { | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
'page' => $page, | |
); | |
$params = self::validate_parameters(self::view_attempt_parameters(), $params); | |
list($attemptobj, $messages) = self::validate_attempt($params); | |
// Log action. | |
quiz_view_attempt($attemptobj); | |
// Update attempt page, throwing an exception if $page is not valid. | |
quiz_set_attempt_page($attemptobj, $params['page']); | |
$result = array(); | |
$result['status'] = true; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the view_attempt return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_returns() { | |
return new external_single_structure( | |
array( | |
'status' => new external_value(PARAM_BOOL, 'status: true if success'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for view_attempt_summary. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_summary_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
) | |
); | |
} | |
/** | |
* Trigger the attempt summary viewed event. | |
* | |
* @param int $attemptid attempt id | |
* @return array of warnings and status result | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_summary($attemptid) { | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
); | |
$params = self::validate_parameters(self::view_attempt_summary_parameters(), $params); | |
list($attemptobj, $messages) = self::validate_attempt($params); | |
// Log action. | |
quiz_view_attempt_summary($attemptobj); | |
$result = array(); | |
$result['status'] = true; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the view_attempt_summary return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_summary_returns() { | |
return new external_single_structure( | |
array( | |
'status' => new external_value(PARAM_BOOL, 'status: true if success'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for view_attempt_review. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_review_parameters() { | |
return new external_function_parameters ( | |
array( | |
'attemptid' => new external_value(PARAM_INT, 'attempt id'), | |
) | |
); | |
} | |
/** | |
* Trigger the attempt reviewed event. | |
* | |
* @param int $attemptid attempt id | |
* @return array of warnings and status result | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_review($attemptid) { | |
$warnings = array(); | |
$params = array( | |
'attemptid' => $attemptid, | |
); | |
$params = self::validate_parameters(self::view_attempt_review_parameters(), $params); | |
list($attemptobj, $displayoptions) = self::validate_attempt_review($params); | |
// Log action. | |
quiz_view_attempt_review($attemptobj); | |
$result = array(); | |
$result['status'] = true; | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the view_attempt_review return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function view_attempt_review_returns() { | |
return new external_single_structure( | |
array( | |
'status' => new external_value(PARAM_BOOL, 'status: true if success'), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
/** | |
* Describes the parameters for view_quiz. | |
* | |
* @return external_external_function_parameters | |
* @since Moodle 3.1 | |
*/ | |
public static function get_quiz_feedback_for_grade_parameters() { | |
return new external_function_parameters ( | |
array( | |
'quizid' => new external_value(PARAM_INT, 'quiz instance id'), | |
'grade' => new external_value(PARAM_FLOAT, 'the grade to check'), | |
) | |
); | |
} | |
/** | |
* Get the feedback text that should be show to a student who got the given grade in the given quiz. | |
* | |
* @param int $quizid quiz instance id | |
* @param float $grade the grade to check | |
* @return array of warnings and status result | |
* @since Moodle 3.1 | |
* @throws moodle_exception | |
*/ | |
public static function get_quiz_feedback_for_grade($quizid, $grade) { | |
global $DB; | |
$params = array( | |
'quizid' => $quizid, | |
'grade' => $grade, | |
); | |
$params = self::validate_parameters(self::get_quiz_feedback_for_grade_parameters(), $params); | |
$warnings = array(); | |
// Request and permission validation. | |
$quiz = $DB->get_record('quiz', array('id' => $params['quizid']), '*', MUST_EXIST); | |
list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); | |
$context = context_module::instance($cm->id); | |
self::validate_context($context); | |
$result = array(); | |
$result['feedbacktext'] = ''; | |
$result['feedbacktextformat'] = FORMAT_MOODLE; | |
$feedback = quiz_feedback_record_for_grade($params['grade'], $quiz); | |
if (!empty($feedback->feedbacktext)) { | |
list($text, $format) = external_format_text($feedback->feedbacktext, $feedback->feedbacktextformat, $context->id, | |
'mod_quiz', 'feedback', $feedback->id); | |
$result['feedbacktext'] = $text; | |
$result['feedbacktextformat'] = $format; | |
} | |
$result['warnings'] = $warnings; | |
return $result; | |
} | |
/** | |
* Describes the get_quiz_feedback_for_grade return value. | |
* | |
* @return external_single_structure | |
* @since Moodle 3.1 | |
*/ | |
public static function get_quiz_feedback_for_grade_returns() { | |
return new external_single_structure( | |
array( | |
'feedbacktext' => new external_value(PARAM_RAW, 'the comment that corresponds to this grade (empty for none)'), | |
'feedbacktextformat' => new external_format_value('feedbacktext', VALUE_OPTIONAL), | |
'warnings' => new external_warnings(), | |
) | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment