Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Quiz external API 19 * 20 * @package mod_quiz 21 * @category external 22 * @copyright 2016 Juan Leyva <juan@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 * @since Moodle 3.1 25 */ 26 27 defined('MOODLE_INTERNAL') || die; 28 29 require_once($CFG->libdir . '/externallib.php'); 30 require_once($CFG->dirroot . '/mod/quiz/locallib.php'); 31 32 /** 33 * Quiz external functions 34 * 35 * @package mod_quiz 36 * @category external 37 * @copyright 2016 Juan Leyva <juan@moodle.com> 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 * @since Moodle 3.1 40 */ 41 class mod_quiz_external extends external_api { 42 43 /** 44 * Describes the parameters for get_quizzes_by_courses. 45 * 46 * @return external_function_parameters 47 * @since Moodle 3.1 48 */ 49 public static function get_quizzes_by_courses_parameters() { 50 return new external_function_parameters ( 51 array( 52 'courseids' => new external_multiple_structure( 53 new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array() 54 ), 55 ) 56 ); 57 } 58 59 /** 60 * Returns a list of quizzes in a provided list of courses, 61 * if no list is provided all quizzes that the user can view will be returned. 62 * 63 * @param array $courseids Array of course ids 64 * @return array of quizzes details 65 * @since Moodle 3.1 66 */ 67 public static function get_quizzes_by_courses($courseids = array()) { 68 global $USER; 69 70 $warnings = array(); 71 $returnedquizzes = array(); 72 73 $params = array( 74 'courseids' => $courseids, 75 ); 76 $params = self::validate_parameters(self::get_quizzes_by_courses_parameters(), $params); 77 78 $mycourses = array(); 79 if (empty($params['courseids'])) { 80 $mycourses = enrol_get_my_courses(); 81 $params['courseids'] = array_keys($mycourses); 82 } 83 84 // Ensure there are courseids to loop through. 85 if (!empty($params['courseids'])) { 86 87 list($courses, $warnings) = external_util::validate_courses($params['courseids'], $mycourses); 88 89 // Get the quizzes in this course, this function checks users visibility permissions. 90 // We can avoid then additional validate_context calls. 91 $quizzes = get_all_instances_in_courses("quiz", $courses); 92 foreach ($quizzes as $quiz) { 93 $context = context_module::instance($quiz->coursemodule); 94 95 // Update quiz with override information. 96 $quiz = quiz_update_effective_access($quiz, $USER->id); 97 98 // Entry to return. 99 $quizdetails = array(); 100 // First, we return information that any user can see in the web interface. 101 $quizdetails['id'] = $quiz->id; 102 $quizdetails['coursemodule'] = $quiz->coursemodule; 103 $quizdetails['course'] = $quiz->course; 104 $quizdetails['name'] = external_format_string($quiz->name, $context->id); 105 106 if (has_capability('mod/quiz:view', $context)) { 107 // Format intro. 108 $options = array('noclean' => true); 109 list($quizdetails['intro'], $quizdetails['introformat']) = 110 external_format_text($quiz->intro, $quiz->introformat, $context->id, 'mod_quiz', 'intro', null, $options); 111 112 $quizdetails['introfiles'] = external_util::get_area_files($context->id, 'mod_quiz', 'intro', false, false); 113 $viewablefields = array('timeopen', 'timeclose', 'grademethod', 'section', 'visible', 'groupmode', 114 'groupingid', 'attempts', 'timelimit', 'grademethod', 'decimalpoints', 115 'questiondecimalpoints', 'sumgrades', 'grade', 'preferredbehaviour'); 116 // Some times this function returns just empty. 117 $hasfeedback = quiz_has_feedback($quiz); 118 $quizdetails['hasfeedback'] = (!empty($hasfeedback)) ? 1 : 0; 119 120 $timenow = time(); 121 $quizobj = quiz::create($quiz->id, $USER->id); 122 $accessmanager = new quiz_access_manager($quizobj, $timenow, has_capability('mod/quiz:ignoretimelimits', 123 $context, null, false)); 124 125 // Fields the user could see if have access to the quiz. 126 if (!$accessmanager->prevent_access()) { 127 $quizdetails['hasquestions'] = (int) $quizobj->has_questions(); 128 $quizdetails['autosaveperiod'] = get_config('quiz', 'autosaveperiod'); 129 130 $additionalfields = array('attemptonlast', 'reviewattempt', 'reviewcorrectness', 'reviewmarks', 131 'reviewspecificfeedback', 'reviewgeneralfeedback', 'reviewrightanswer', 132 'reviewoverallfeedback', 'questionsperpage', 'navmethod', 133 'browsersecurity', 'delay1', 'delay2', 'showuserpicture', 'showblocks', 134 'completionattemptsexhausted', 'completionpass', 'overduehandling', 135 'graceperiod', 'canredoquestions', 'allowofflineattempts'); 136 $viewablefields = array_merge($viewablefields, $additionalfields); 137 } 138 139 // Fields only for managers. 140 if (has_capability('moodle/course:manageactivities', $context)) { 141 $additionalfields = array('shuffleanswers', 'timecreated', 'timemodified', 'password', 'subnet'); 142 $viewablefields = array_merge($viewablefields, $additionalfields); 143 } 144 145 foreach ($viewablefields as $field) { 146 $quizdetails[$field] = $quiz->{$field}; 147 } 148 } 149 $returnedquizzes[] = $quizdetails; 150 } 151 } 152 $result = array(); 153 $result['quizzes'] = $returnedquizzes; 154 $result['warnings'] = $warnings; 155 return $result; 156 } 157 158 /** 159 * Describes the get_quizzes_by_courses return value. 160 * 161 * @return external_single_structure 162 * @since Moodle 3.1 163 */ 164 public static function get_quizzes_by_courses_returns() { 165 return new external_single_structure( 166 array( 167 'quizzes' => new external_multiple_structure( 168 new external_single_structure( 169 array( 170 'id' => new external_value(PARAM_INT, 'Standard Moodle primary key.'), 171 'course' => new external_value(PARAM_INT, 'Foreign key reference to the course this quiz is part of.'), 172 'coursemodule' => new external_value(PARAM_INT, 'Course module id.'), 173 'name' => new external_value(PARAM_RAW, 'Quiz name.'), 174 'intro' => new external_value(PARAM_RAW, 'Quiz introduction text.', VALUE_OPTIONAL), 175 'introformat' => new external_format_value('intro', VALUE_OPTIONAL), 176 'introfiles' => new external_files('Files in the introduction text', VALUE_OPTIONAL), 177 'timeopen' => new external_value(PARAM_INT, 'The time when this quiz opens. (0 = no restriction.)', 178 VALUE_OPTIONAL), 179 'timeclose' => new external_value(PARAM_INT, 'The time when this quiz closes. (0 = no restriction.)', 180 VALUE_OPTIONAL), 181 'timelimit' => new external_value(PARAM_INT, 'The time limit for quiz attempts, in seconds.', 182 VALUE_OPTIONAL), 183 'overduehandling' => new external_value(PARAM_ALPHA, 'The method used to handle overdue attempts. 184 \'autosubmit\', \'graceperiod\' or \'autoabandon\'.', 185 VALUE_OPTIONAL), 186 'graceperiod' => new external_value(PARAM_INT, 'The amount of time (in seconds) after the time limit 187 runs out during which attempts can still be submitted, 188 if overduehandling is set to allow it.', VALUE_OPTIONAL), 189 'preferredbehaviour' => new external_value(PARAM_ALPHANUMEXT, 'The behaviour to ask questions to use.', 190 VALUE_OPTIONAL), 191 'canredoquestions' => new external_value(PARAM_INT, 'Allows students to redo any completed question 192 within a quiz attempt.', VALUE_OPTIONAL), 193 'attempts' => new external_value(PARAM_INT, 'The maximum number of attempts a student is allowed.', 194 VALUE_OPTIONAL), 195 'attemptonlast' => new external_value(PARAM_INT, 'Whether subsequent attempts start from the answer 196 to the previous attempt (1) or start blank (0).', 197 VALUE_OPTIONAL), 198 'grademethod' => new external_value(PARAM_INT, 'One of the values QUIZ_GRADEHIGHEST, QUIZ_GRADEAVERAGE, 199 QUIZ_ATTEMPTFIRST or QUIZ_ATTEMPTLAST.', VALUE_OPTIONAL), 200 'decimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when displaying 201 grades.', VALUE_OPTIONAL), 202 'questiondecimalpoints' => new external_value(PARAM_INT, 'Number of decimal points to use when 203 displaying question grades. 204 (-1 means use decimalpoints.)', VALUE_OPTIONAL), 205 'reviewattempt' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz 206 attempts at various times. This is a bit field, decoded by the 207 mod_quiz_display_options class. It is formed by ORing together 208 the constants defined there.', VALUE_OPTIONAL), 209 'reviewcorrectness' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz 210 attempts at various times. 211 A bit field, like reviewattempt.', VALUE_OPTIONAL), 212 'reviewmarks' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz attempts 213 at various times. A bit field, like reviewattempt.', 214 VALUE_OPTIONAL), 215 'reviewspecificfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their 216 quiz attempts at various times. A bit field, like 217 reviewattempt.', VALUE_OPTIONAL), 218 'reviewgeneralfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their 219 quiz attempts at various times. A bit field, like 220 reviewattempt.', VALUE_OPTIONAL), 221 'reviewrightanswer' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz 222 attempts at various times. A bit field, like 223 reviewattempt.', VALUE_OPTIONAL), 224 'reviewoverallfeedback' => new external_value(PARAM_INT, 'Whether users are allowed to review their quiz 225 attempts at various times. A bit field, like 226 reviewattempt.', VALUE_OPTIONAL), 227 'questionsperpage' => new external_value(PARAM_INT, 'How often to insert a page break when editing 228 the quiz, or when shuffling the question order.', 229 VALUE_OPTIONAL), 230 'navmethod' => new external_value(PARAM_ALPHA, 'Any constraints on how the user is allowed to navigate 231 around the quiz. Currently recognised values are 232 \'free\' and \'seq\'.', VALUE_OPTIONAL), 233 'shuffleanswers' => new external_value(PARAM_INT, 'Whether the parts of the question should be shuffled, 234 in those question types that support it.', VALUE_OPTIONAL), 235 'sumgrades' => new external_value(PARAM_FLOAT, 'The total of all the question instance maxmarks.', 236 VALUE_OPTIONAL), 237 'grade' => new external_value(PARAM_FLOAT, 'The total that the quiz overall grade is scaled to be 238 out of.', VALUE_OPTIONAL), 239 'timecreated' => new external_value(PARAM_INT, 'The time when the quiz was added to the course.', 240 VALUE_OPTIONAL), 241 'timemodified' => new external_value(PARAM_INT, 'Last modified time.', 242 VALUE_OPTIONAL), 243 'password' => new external_value(PARAM_RAW, 'A password that the student must enter before starting or 244 continuing a quiz attempt.', VALUE_OPTIONAL), 245 'subnet' => new external_value(PARAM_RAW, 'Used to restrict the IP addresses from which this quiz can 246 be attempted. The format is as requried by the address_in_subnet 247 function.', VALUE_OPTIONAL), 248 'browsersecurity' => new external_value(PARAM_ALPHANUMEXT, 'Restriciton on the browser the student must 249 use. E.g. \'securewindow\'.', VALUE_OPTIONAL), 250 'delay1' => new external_value(PARAM_INT, 'Delay that must be left between the first and second attempt, 251 in seconds.', VALUE_OPTIONAL), 252 'delay2' => new external_value(PARAM_INT, 'Delay that must be left between the second and subsequent 253 attempt, in seconds.', VALUE_OPTIONAL), 254 'showuserpicture' => new external_value(PARAM_INT, 'Option to show the user\'s picture during the 255 attempt and on the review page.', VALUE_OPTIONAL), 256 'showblocks' => new external_value(PARAM_INT, 'Whether blocks should be shown on the attempt.php and 257 review.php pages.', VALUE_OPTIONAL), 258 'completionattemptsexhausted' => new external_value(PARAM_INT, 'Mark quiz complete when the student has 259 exhausted the maximum number of attempts', 260 VALUE_OPTIONAL), 261 'completionpass' => new external_value(PARAM_INT, 'Whether to require passing grade', VALUE_OPTIONAL), 262 'allowofflineattempts' => new external_value(PARAM_INT, 'Whether to allow the quiz to be attempted 263 offline in the mobile app', VALUE_OPTIONAL), 264 'autosaveperiod' => new external_value(PARAM_INT, 'Auto-save delay', VALUE_OPTIONAL), 265 'hasfeedback' => new external_value(PARAM_INT, 'Whether the quiz has any non-blank feedback text', 266 VALUE_OPTIONAL), 267 'hasquestions' => new external_value(PARAM_INT, 'Whether the quiz has questions', VALUE_OPTIONAL), 268 'section' => new external_value(PARAM_INT, 'Course section id', VALUE_OPTIONAL), 269 'visible' => new external_value(PARAM_INT, 'Module visibility', VALUE_OPTIONAL), 270 'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL), 271 'groupingid' => new external_value(PARAM_INT, 'Grouping id', VALUE_OPTIONAL), 272 ) 273 ) 274 ), 275 'warnings' => new external_warnings(), 276 ) 277 ); 278 } 279 280 281 /** 282 * Utility function for validating a quiz. 283 * 284 * @param int $quizid quiz instance id 285 * @return array array containing the quiz, course, context and course module objects 286 * @since Moodle 3.1 287 */ 288 protected static function validate_quiz($quizid) { 289 global $DB; 290 291 // Request and permission validation. 292 $quiz = $DB->get_record('quiz', array('id' => $quizid), '*', MUST_EXIST); 293 list($course, $cm) = get_course_and_cm_from_instance($quiz, 'quiz'); 294 295 $context = context_module::instance($cm->id); 296 self::validate_context($context); 297 298 return array($quiz, $course, $cm, $context); 299 } 300 301 /** 302 * Describes the parameters for view_quiz. 303 * 304 * @return external_function_parameters 305 * @since Moodle 3.1 306 */ 307 public static function view_quiz_parameters() { 308 return new external_function_parameters ( 309 array( 310 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 311 ) 312 ); 313 } 314 315 /** 316 * Trigger the course module viewed event and update the module completion status. 317 * 318 * @param int $quizid quiz instance id 319 * @return array of warnings and status result 320 * @since Moodle 3.1 321 * @throws moodle_exception 322 */ 323 public static function view_quiz($quizid) { 324 global $DB; 325 326 $params = self::validate_parameters(self::view_quiz_parameters(), array('quizid' => $quizid)); 327 $warnings = array(); 328 329 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 330 331 // Trigger course_module_viewed event and completion. 332 quiz_view($quiz, $course, $cm, $context); 333 334 $result = array(); 335 $result['status'] = true; 336 $result['warnings'] = $warnings; 337 return $result; 338 } 339 340 /** 341 * Describes the view_quiz return value. 342 * 343 * @return external_single_structure 344 * @since Moodle 3.1 345 */ 346 public static function view_quiz_returns() { 347 return new external_single_structure( 348 array( 349 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 350 'warnings' => new external_warnings(), 351 ) 352 ); 353 } 354 355 /** 356 * Describes the parameters for get_user_attempts. 357 * 358 * @return external_function_parameters 359 * @since Moodle 3.1 360 */ 361 public static function get_user_attempts_parameters() { 362 return new external_function_parameters ( 363 array( 364 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 365 'userid' => new external_value(PARAM_INT, 'user id, empty for current user', VALUE_DEFAULT, 0), 366 'status' => new external_value(PARAM_ALPHA, 'quiz status: all, finished or unfinished', VALUE_DEFAULT, 'finished'), 367 'includepreviews' => new external_value(PARAM_BOOL, 'whether to include previews or not', VALUE_DEFAULT, false), 368 369 ) 370 ); 371 } 372 373 /** 374 * Return a list of attempts for the given quiz and user. 375 * 376 * @param int $quizid quiz instance id 377 * @param int $userid user id 378 * @param string $status quiz status: all, finished or unfinished 379 * @param bool $includepreviews whether to include previews or not 380 * @return array of warnings and the list of attempts 381 * @since Moodle 3.1 382 * @throws invalid_parameter_exception 383 */ 384 public static function get_user_attempts($quizid, $userid = 0, $status = 'finished', $includepreviews = false) { 385 global $DB, $USER; 386 387 $warnings = array(); 388 389 $params = array( 390 'quizid' => $quizid, 391 'userid' => $userid, 392 'status' => $status, 393 'includepreviews' => $includepreviews, 394 ); 395 $params = self::validate_parameters(self::get_user_attempts_parameters(), $params); 396 397 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 398 399 if (!in_array($params['status'], array('all', 'finished', 'unfinished'))) { 400 throw new invalid_parameter_exception('Invalid status value'); 401 } 402 403 // Default value for userid. 404 if (empty($params['userid'])) { 405 $params['userid'] = $USER->id; 406 } 407 408 $user = core_user::get_user($params['userid'], '*', MUST_EXIST); 409 core_user::require_active_user($user); 410 411 // Extra checks so only users with permissions can view other users attempts. 412 if ($USER->id != $user->id) { 413 require_capability('mod/quiz:viewreports', $context); 414 } 415 416 // Update quiz with override information. 417 $quiz = quiz_update_effective_access($quiz, $params['userid']); 418 $attempts = quiz_get_user_attempts($quiz->id, $user->id, $params['status'], $params['includepreviews']); 419 $attemptresponse = []; 420 foreach ($attempts as $attempt) { 421 $reviewoptions = quiz_get_review_options($quiz, $attempt, $context); 422 if (!has_capability('mod/quiz:viewreports', $context) && 423 ($reviewoptions->marks < question_display_options::MARK_AND_MAX || $attempt->state != quiz_attempt::FINISHED)) { 424 // Blank the mark if the teacher does not allow it. 425 $attempt->sumgrades = null; 426 } 427 $attemptresponse[] = $attempt; 428 } 429 $result = array(); 430 $result['attempts'] = $attemptresponse; 431 $result['warnings'] = $warnings; 432 return $result; 433 } 434 435 /** 436 * Describes a single attempt structure. 437 * 438 * @return external_single_structure the attempt structure 439 */ 440 private static function attempt_structure() { 441 return new external_single_structure( 442 array( 443 'id' => new external_value(PARAM_INT, 'Attempt id.', VALUE_OPTIONAL), 444 'quiz' => new external_value(PARAM_INT, 'Foreign key reference to the quiz that was attempted.', 445 VALUE_OPTIONAL), 446 'userid' => new external_value(PARAM_INT, 'Foreign key reference to the user whose attempt this is.', 447 VALUE_OPTIONAL), 448 'attempt' => new external_value(PARAM_INT, 'Sequentially numbers this students attempts at this quiz.', 449 VALUE_OPTIONAL), 450 'uniqueid' => new external_value(PARAM_INT, 'Foreign key reference to the question_usage that holds the 451 details of the the question_attempts that make up this quiz 452 attempt.', VALUE_OPTIONAL), 453 'layout' => new external_value(PARAM_RAW, 'Attempt layout.', VALUE_OPTIONAL), 454 'currentpage' => new external_value(PARAM_INT, 'Attempt current page.', VALUE_OPTIONAL), 455 'preview' => new external_value(PARAM_INT, 'Whether is a preview attempt or not.', VALUE_OPTIONAL), 456 'state' => new external_value(PARAM_ALPHA, 'The current state of the attempts. \'inprogress\', 457 \'overdue\', \'finished\' or \'abandoned\'.', VALUE_OPTIONAL), 458 'timestart' => new external_value(PARAM_INT, 'Time when the attempt was started.', VALUE_OPTIONAL), 459 'timefinish' => new external_value(PARAM_INT, 'Time when the attempt was submitted. 460 0 if the attempt has not been submitted yet.', VALUE_OPTIONAL), 461 'timemodified' => new external_value(PARAM_INT, 'Last modified time.', VALUE_OPTIONAL), 462 'timemodifiedoffline' => new external_value(PARAM_INT, 'Last modified time via webservices.', VALUE_OPTIONAL), 463 'timecheckstate' => new external_value(PARAM_INT, 'Next time quiz cron should check attempt for 464 state changes. NULL means never check.', VALUE_OPTIONAL), 465 'sumgrades' => new external_value(PARAM_FLOAT, 'Total marks for this attempt.', VALUE_OPTIONAL), 466 ) 467 ); 468 } 469 470 /** 471 * Describes the get_user_attempts return value. 472 * 473 * @return external_single_structure 474 * @since Moodle 3.1 475 */ 476 public static function get_user_attempts_returns() { 477 return new external_single_structure( 478 array( 479 'attempts' => new external_multiple_structure(self::attempt_structure()), 480 'warnings' => new external_warnings(), 481 ) 482 ); 483 } 484 485 /** 486 * Describes the parameters for get_user_best_grade. 487 * 488 * @return external_function_parameters 489 * @since Moodle 3.1 490 */ 491 public static function get_user_best_grade_parameters() { 492 return new external_function_parameters ( 493 array( 494 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 495 'userid' => new external_value(PARAM_INT, 'user id', VALUE_DEFAULT, 0), 496 ) 497 ); 498 } 499 500 /** 501 * Get the best current grade for the given user on a quiz. 502 * 503 * @param int $quizid quiz instance id 504 * @param int $userid user id 505 * @return array of warnings and the grade information 506 * @since Moodle 3.1 507 */ 508 public static function get_user_best_grade($quizid, $userid = 0) { 509 global $DB, $USER; 510 511 $warnings = array(); 512 513 $params = array( 514 'quizid' => $quizid, 515 'userid' => $userid, 516 ); 517 $params = self::validate_parameters(self::get_user_best_grade_parameters(), $params); 518 519 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 520 521 // Default value for userid. 522 if (empty($params['userid'])) { 523 $params['userid'] = $USER->id; 524 } 525 526 $user = core_user::get_user($params['userid'], '*', MUST_EXIST); 527 core_user::require_active_user($user); 528 529 // Extra checks so only users with permissions can view other users attempts. 530 if ($USER->id != $user->id) { 531 require_capability('mod/quiz:viewreports', $context); 532 } 533 534 $result = array(); 535 536 // This code was mostly copied from mod/quiz/view.php. We need to make the web service logic consistent. 537 // Get this user's attempts. 538 $attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all'); 539 $canviewgrade = false; 540 if ($attempts) { 541 if ($USER->id != $user->id) { 542 // No need to check the permission here. We did it at by require_capability('mod/quiz:viewreports', $context). 543 $canviewgrade = true; 544 } else { 545 // Work out which columns we need, taking account what data is available in each attempt. 546 [$notused, $alloptions] = quiz_get_combined_reviewoptions($quiz, $attempts); 547 $canviewgrade = $alloptions->marks >= question_display_options::MARK_AND_MAX; 548 } 549 } 550 551 $grade = $canviewgrade ? quiz_get_best_grade($quiz, $user->id) : null; 552 553 if ($grade === null) { 554 $result['hasgrade'] = false; 555 } else { 556 $result['hasgrade'] = true; 557 $result['grade'] = $grade; 558 } 559 $result['warnings'] = $warnings; 560 return $result; 561 } 562 563 /** 564 * Describes the get_user_best_grade return value. 565 * 566 * @return external_single_structure 567 * @since Moodle 3.1 568 */ 569 public static function get_user_best_grade_returns() { 570 return new external_single_structure( 571 array( 572 'hasgrade' => new external_value(PARAM_BOOL, 'Whether the user has a grade on the given quiz.'), 573 'grade' => new external_value(PARAM_FLOAT, 'The grade (only if the user has a grade).', VALUE_OPTIONAL), 574 'warnings' => new external_warnings(), 575 ) 576 ); 577 } 578 579 /** 580 * Describes the parameters for get_combined_review_options. 581 * 582 * @return external_function_parameters 583 * @since Moodle 3.1 584 */ 585 public static function get_combined_review_options_parameters() { 586 return new external_function_parameters ( 587 array( 588 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 589 'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0), 590 591 ) 592 ); 593 } 594 595 /** 596 * Combines the review options from a number of different quiz attempts. 597 * 598 * @param int $quizid quiz instance id 599 * @param int $userid user id (empty for current user) 600 * @return array of warnings and the review options 601 * @since Moodle 3.1 602 */ 603 public static function get_combined_review_options($quizid, $userid = 0) { 604 global $DB, $USER; 605 606 $warnings = array(); 607 608 $params = array( 609 'quizid' => $quizid, 610 'userid' => $userid, 611 ); 612 $params = self::validate_parameters(self::get_combined_review_options_parameters(), $params); 613 614 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 615 616 // Default value for userid. 617 if (empty($params['userid'])) { 618 $params['userid'] = $USER->id; 619 } 620 621 $user = core_user::get_user($params['userid'], '*', MUST_EXIST); 622 core_user::require_active_user($user); 623 624 // Extra checks so only users with permissions can view other users attempts. 625 if ($USER->id != $user->id) { 626 require_capability('mod/quiz:viewreports', $context); 627 } 628 629 $attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all', true); 630 631 $result = array(); 632 $result['someoptions'] = []; 633 $result['alloptions'] = []; 634 635 list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts); 636 637 foreach (array('someoptions', 'alloptions') as $typeofoption) { 638 foreach ($$typeofoption as $key => $value) { 639 $result[$typeofoption][] = array( 640 "name" => $key, 641 "value" => (!empty($value)) ? $value : 0 642 ); 643 } 644 } 645 646 $result['warnings'] = $warnings; 647 return $result; 648 } 649 650 /** 651 * Describes the get_combined_review_options return value. 652 * 653 * @return external_single_structure 654 * @since Moodle 3.1 655 */ 656 public static function get_combined_review_options_returns() { 657 return new external_single_structure( 658 array( 659 'someoptions' => new external_multiple_structure( 660 new external_single_structure( 661 array( 662 'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), 663 'value' => new external_value(PARAM_INT, 'option value'), 664 ) 665 ) 666 ), 667 'alloptions' => new external_multiple_structure( 668 new external_single_structure( 669 array( 670 'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), 671 'value' => new external_value(PARAM_INT, 'option value'), 672 ) 673 ) 674 ), 675 'warnings' => new external_warnings(), 676 ) 677 ); 678 } 679 680 /** 681 * Describes the parameters for start_attempt. 682 * 683 * @return external_function_parameters 684 * @since Moodle 3.1 685 */ 686 public static function start_attempt_parameters() { 687 return new external_function_parameters ( 688 array( 689 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 690 'preflightdata' => new external_multiple_structure( 691 new external_single_structure( 692 array( 693 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 694 'value' => new external_value(PARAM_RAW, 'data value'), 695 ) 696 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 697 ), 698 'forcenew' => new external_value(PARAM_BOOL, 'Whether to force a new attempt or not.', VALUE_DEFAULT, false), 699 700 ) 701 ); 702 } 703 704 /** 705 * Starts a new attempt at a quiz. 706 * 707 * @param int $quizid quiz instance id 708 * @param array $preflightdata preflight required data (like passwords) 709 * @param bool $forcenew Whether to force a new attempt or not. 710 * @return array of warnings and the attempt basic data 711 * @since Moodle 3.1 712 * @throws moodle_quiz_exception 713 */ 714 public static function start_attempt($quizid, $preflightdata = array(), $forcenew = false) { 715 global $DB, $USER; 716 717 $warnings = array(); 718 $attempt = array(); 719 720 $params = array( 721 'quizid' => $quizid, 722 'preflightdata' => $preflightdata, 723 'forcenew' => $forcenew, 724 ); 725 $params = self::validate_parameters(self::start_attempt_parameters(), $params); 726 $forcenew = $params['forcenew']; 727 728 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 729 730 $quizobj = quiz::create($cm->instance, $USER->id); 731 732 // Check questions. 733 if (!$quizobj->has_questions()) { 734 throw new moodle_quiz_exception($quizobj, 'noquestionsfound'); 735 } 736 737 // Create an object to manage all the other (non-roles) access rules. 738 $timenow = time(); 739 $accessmanager = $quizobj->get_access_manager($timenow); 740 741 // Validate permissions for creating a new attempt and start a new preview attempt if required. 742 list($currentattemptid, $attemptnumber, $lastattempt, $messages, $page) = 743 quiz_validate_new_attempt($quizobj, $accessmanager, $forcenew, -1, false); 744 745 // Check access. 746 if (!$quizobj->is_preview_user() && $messages) { 747 // Create warnings with the exact messages. 748 foreach ($messages as $message) { 749 $warnings[] = array( 750 'item' => 'quiz', 751 'itemid' => $quiz->id, 752 'warningcode' => '1', 753 'message' => clean_text($message, PARAM_TEXT) 754 ); 755 } 756 } else { 757 if ($accessmanager->is_preflight_check_required($currentattemptid)) { 758 // Need to do some checks before allowing the user to continue. 759 760 $provideddata = array(); 761 foreach ($params['preflightdata'] as $data) { 762 $provideddata[$data['name']] = $data['value']; 763 } 764 765 $errors = $accessmanager->validate_preflight_check($provideddata, [], $currentattemptid); 766 767 if (!empty($errors)) { 768 throw new moodle_quiz_exception($quizobj, array_shift($errors)); 769 } 770 771 // Pre-flight check passed. 772 $accessmanager->notify_preflight_check_passed($currentattemptid); 773 } 774 775 if ($currentattemptid) { 776 if ($lastattempt->state == quiz_attempt::OVERDUE) { 777 throw new moodle_quiz_exception($quizobj, 'stateoverdue'); 778 } else { 779 throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress'); 780 } 781 } 782 $offlineattempt = WS_SERVER ? true : false; 783 $attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt, $offlineattempt); 784 } 785 786 $result = array(); 787 $result['attempt'] = $attempt; 788 $result['warnings'] = $warnings; 789 return $result; 790 } 791 792 /** 793 * Describes the start_attempt return value. 794 * 795 * @return external_single_structure 796 * @since Moodle 3.1 797 */ 798 public static function start_attempt_returns() { 799 return new external_single_structure( 800 array( 801 'attempt' => self::attempt_structure(), 802 'warnings' => new external_warnings(), 803 ) 804 ); 805 } 806 807 /** 808 * Utility function for validating a given attempt 809 * 810 * @param array $params array of parameters including the attemptid and preflight data 811 * @param bool $checkaccessrules whether to check the quiz access rules or not 812 * @param bool $failifoverdue whether to return error if the attempt is overdue 813 * @return array containing the attempt object and access messages 814 * @throws moodle_quiz_exception 815 * @since Moodle 3.1 816 */ 817 protected static function validate_attempt($params, $checkaccessrules = true, $failifoverdue = true) { 818 global $USER; 819 820 $attemptobj = quiz_attempt::create($params['attemptid']); 821 822 $context = context_module::instance($attemptobj->get_cm()->id); 823 self::validate_context($context); 824 825 // Check that this attempt belongs to this user. 826 if ($attemptobj->get_userid() != $USER->id) { 827 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); 828 } 829 830 // General capabilities check. 831 $ispreviewuser = $attemptobj->is_preview_user(); 832 if (!$ispreviewuser) { 833 $attemptobj->require_capability('mod/quiz:attempt'); 834 } 835 836 // Check the access rules. 837 $accessmanager = $attemptobj->get_access_manager(time()); 838 $messages = array(); 839 if ($checkaccessrules) { 840 // If the attempt is now overdue, or abandoned, deal with that. 841 $attemptobj->handle_if_time_expired(time(), true); 842 843 $messages = $accessmanager->prevent_access(); 844 if (!$ispreviewuser && $messages) { 845 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror'); 846 } 847 } 848 849 // Attempt closed?. 850 if ($attemptobj->is_finished()) { 851 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed'); 852 } else if ($failifoverdue && $attemptobj->get_state() == quiz_attempt::OVERDUE) { 853 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue'); 854 } 855 856 // User submitted data (like the quiz password). 857 if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) { 858 $provideddata = array(); 859 foreach ($params['preflightdata'] as $data) { 860 $provideddata[$data['name']] = $data['value']; 861 } 862 863 $errors = $accessmanager->validate_preflight_check($provideddata, [], $params['attemptid']); 864 if (!empty($errors)) { 865 throw new moodle_quiz_exception($attemptobj->get_quizobj(), array_shift($errors)); 866 } 867 // Pre-flight check passed. 868 $accessmanager->notify_preflight_check_passed($params['attemptid']); 869 } 870 871 if (isset($params['page'])) { 872 // Check if the page is out of range. 873 if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) { 874 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number'); 875 } 876 877 // Prevent out of sequence access. 878 if (!$attemptobj->check_page_access($params['page'])) { 879 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access'); 880 } 881 882 // Check slots. 883 $slots = $attemptobj->get_slots($params['page']); 884 885 if (empty($slots)) { 886 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound'); 887 } 888 } 889 890 return array($attemptobj, $messages); 891 } 892 893 /** 894 * Describes a single question structure. 895 * 896 * @return external_single_structure the question data. Some fields may not be returned depending on the quiz display settings. 897 * @since Moodle 3.1 898 * @since Moodle 3.2 blockedbyprevious parameter added. 899 */ 900 private static function question_structure() { 901 return new external_single_structure( 902 array( 903 'slot' => new external_value(PARAM_INT, 'slot number'), 904 'type' => new external_value(PARAM_ALPHANUMEXT, 'question type, i.e: multichoice'), 905 'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'), 906 'html' => new external_value(PARAM_RAW, 'the question rendered'), 907 'responsefileareas' => new external_multiple_structure( 908 new external_single_structure( 909 array( 910 'area' => new external_value(PARAM_NOTAGS, 'File area name'), 911 'files' => new external_files('Response files for the question', VALUE_OPTIONAL), 912 ) 913 ), 'Response file areas including files', VALUE_OPTIONAL 914 ), 915 'sequencecheck' => new external_value(PARAM_INT, 'the number of real steps in this attempt', VALUE_OPTIONAL), 916 'lastactiontime' => new external_value(PARAM_INT, 'the timestamp of the most recent step in this question attempt', 917 VALUE_OPTIONAL), 918 'hasautosavedstep' => new external_value(PARAM_BOOL, 'whether this question attempt has autosaved data', 919 VALUE_OPTIONAL), 920 'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'), 921 'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL), 922 'state' => new external_value(PARAM_ALPHA, 'the state where the question is in. 923 It will not be returned if the user cannot see it due to the quiz display correctness settings.', 924 VALUE_OPTIONAL), 925 'status' => new external_value(PARAM_RAW, 'current formatted state of the question', VALUE_OPTIONAL), 926 'blockedbyprevious' => new external_value(PARAM_BOOL, 'whether the question is blocked by the previous question', 927 VALUE_OPTIONAL), 928 'mark' => new external_value(PARAM_RAW, 'the mark awarded. 929 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 930 'maxmark' => new external_value(PARAM_FLOAT, 'the maximum mark possible for this question attempt. 931 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 932 'settings' => new external_value(PARAM_RAW, 'Question settings (JSON encoded).', VALUE_OPTIONAL), 933 ), 934 'The question data. Some fields may not be returned depending on the quiz display settings.' 935 ); 936 } 937 938 /** 939 * Return questions information for a given attempt. 940 * 941 * @param quiz_attempt $attemptobj the quiz attempt object 942 * @param bool $review whether if we are in review mode or not 943 * @param mixed $page string 'all' or integer page number 944 * @return array array of questions including data 945 */ 946 private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all') { 947 global $PAGE; 948 949 $questions = array(); 950 $contextid = $attemptobj->get_quizobj()->get_context()->id; 951 $displayoptions = $attemptobj->get_display_options($review); 952 $renderer = $PAGE->get_renderer('mod_quiz'); 953 $contextid = $attemptobj->get_quizobj()->get_context()->id; 954 955 foreach ($attemptobj->get_slots($page) as $slot) { 956 $qtype = $attemptobj->get_question_type_name($slot); 957 $qattempt = $attemptobj->get_question_attempt($slot); 958 $questiondef = $qattempt->get_question(true); 959 960 // Get response files (for questions like essay that allows attachments). 961 $responsefileareas = []; 962 foreach (question_bank::get_qtype($qtype)->response_file_areas() as $area) { 963 if ($files = $attemptobj->get_question_attempt($slot)->get_last_qt_files($area, $contextid)) { 964 $responsefileareas[$area]['area'] = $area; 965 $responsefileareas[$area]['files'] = []; 966 967 foreach ($files as $file) { 968 $responsefileareas[$area]['files'][] = array( 969 'filename' => $file->get_filename(), 970 'fileurl' => $qattempt->get_response_file_url($file), 971 'filesize' => $file->get_filesize(), 972 'filepath' => $file->get_filepath(), 973 'mimetype' => $file->get_mimetype(), 974 'timemodified' => $file->get_timemodified(), 975 ); 976 } 977 } 978 } 979 980 // Check display settings for question. 981 $settings = $questiondef->get_question_definition_for_external_rendering($qattempt, $displayoptions); 982 983 $question = array( 984 'slot' => $slot, 985 'type' => $qtype, 986 'page' => $attemptobj->get_question_page($slot), 987 'flagged' => $attemptobj->is_question_flagged($slot), 988 'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code(), 989 'responsefileareas' => $responsefileareas, 990 'sequencecheck' => $qattempt->get_sequence_check_count(), 991 'lastactiontime' => $qattempt->get_last_step()->get_timecreated(), 992 'hasautosavedstep' => $qattempt->has_autosaved_step(), 993 'settings' => !empty($settings) ? json_encode($settings) : null, 994 ); 995 996 if ($attemptobj->is_real_question($slot)) { 997 $question['number'] = $attemptobj->get_question_number($slot); 998 $showcorrectness = $displayoptions->correctness && $qattempt->has_marks(); 999 if ($showcorrectness) { 1000 $question['state'] = (string) $attemptobj->get_question_state($slot); 1001 } 1002 $question['status'] = $attemptobj->get_question_status($slot, $displayoptions->correctness); 1003 $question['blockedbyprevious'] = $attemptobj->is_blocked_by_previous_question($slot); 1004 } 1005 if ($displayoptions->marks >= question_display_options::MAX_ONLY) { 1006 $question['maxmark'] = $qattempt->get_max_mark(); 1007 } 1008 if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) { 1009 $question['mark'] = $attemptobj->get_question_mark($slot); 1010 } 1011 1012 $questions[] = $question; 1013 } 1014 return $questions; 1015 } 1016 1017 /** 1018 * Describes the parameters for get_attempt_data. 1019 * 1020 * @return external_function_parameters 1021 * @since Moodle 3.1 1022 */ 1023 public static function get_attempt_data_parameters() { 1024 return new external_function_parameters ( 1025 array( 1026 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1027 'page' => new external_value(PARAM_INT, 'page number'), 1028 'preflightdata' => new external_multiple_structure( 1029 new external_single_structure( 1030 array( 1031 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1032 'value' => new external_value(PARAM_RAW, 'data value'), 1033 ) 1034 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1035 ) 1036 ) 1037 ); 1038 } 1039 1040 /** 1041 * Returns information for the given attempt page for a quiz attempt in progress. 1042 * 1043 * @param int $attemptid attempt id 1044 * @param int $page page number 1045 * @param array $preflightdata preflight required data (like passwords) 1046 * @return array of warnings and the attempt data, next page, message and questions 1047 * @since Moodle 3.1 1048 * @throws moodle_quiz_exceptions 1049 */ 1050 public static function get_attempt_data($attemptid, $page, $preflightdata = array()) { 1051 1052 $warnings = array(); 1053 1054 $params = array( 1055 'attemptid' => $attemptid, 1056 'page' => $page, 1057 'preflightdata' => $preflightdata, 1058 ); 1059 $params = self::validate_parameters(self::get_attempt_data_parameters(), $params); 1060 1061 list($attemptobj, $messages) = self::validate_attempt($params); 1062 1063 if ($attemptobj->is_last_page($params['page'])) { 1064 $nextpage = -1; 1065 } else { 1066 $nextpage = $params['page'] + 1; 1067 } 1068 1069 $result = array(); 1070 $result['attempt'] = $attemptobj->get_attempt(); 1071 $result['messages'] = $messages; 1072 $result['nextpage'] = $nextpage; 1073 $result['warnings'] = $warnings; 1074 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, $params['page']); 1075 1076 return $result; 1077 } 1078 1079 /** 1080 * Describes the get_attempt_data return value. 1081 * 1082 * @return external_single_structure 1083 * @since Moodle 3.1 1084 */ 1085 public static function get_attempt_data_returns() { 1086 return new external_single_structure( 1087 array( 1088 'attempt' => self::attempt_structure(), 1089 'messages' => new external_multiple_structure( 1090 new external_value(PARAM_TEXT, 'access message'), 1091 'access messages, will only be returned for users with mod/quiz:preview capability, 1092 for other users this method will throw an exception if there are messages'), 1093 'nextpage' => new external_value(PARAM_INT, 'next page number'), 1094 'questions' => new external_multiple_structure(self::question_structure()), 1095 'warnings' => new external_warnings(), 1096 ) 1097 ); 1098 } 1099 1100 /** 1101 * Describes the parameters for get_attempt_summary. 1102 * 1103 * @return external_function_parameters 1104 * @since Moodle 3.1 1105 */ 1106 public static function get_attempt_summary_parameters() { 1107 return new external_function_parameters ( 1108 array( 1109 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1110 'preflightdata' => new external_multiple_structure( 1111 new external_single_structure( 1112 array( 1113 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1114 'value' => new external_value(PARAM_RAW, 'data value'), 1115 ) 1116 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1117 ) 1118 ) 1119 ); 1120 } 1121 1122 /** 1123 * Returns a summary of a quiz attempt before it is submitted. 1124 * 1125 * @param int $attemptid attempt id 1126 * @param int $preflightdata preflight required data (like passwords) 1127 * @return array of warnings and the attempt summary data for each question 1128 * @since Moodle 3.1 1129 */ 1130 public static function get_attempt_summary($attemptid, $preflightdata = array()) { 1131 1132 $warnings = array(); 1133 1134 $params = array( 1135 'attemptid' => $attemptid, 1136 'preflightdata' => $preflightdata, 1137 ); 1138 $params = self::validate_parameters(self::get_attempt_summary_parameters(), $params); 1139 1140 list($attemptobj, $messages) = self::validate_attempt($params, true, false); 1141 1142 $result = array(); 1143 $result['warnings'] = $warnings; 1144 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all'); 1145 1146 return $result; 1147 } 1148 1149 /** 1150 * Describes the get_attempt_summary return value. 1151 * 1152 * @return external_single_structure 1153 * @since Moodle 3.1 1154 */ 1155 public static function get_attempt_summary_returns() { 1156 return new external_single_structure( 1157 array( 1158 'questions' => new external_multiple_structure(self::question_structure()), 1159 'warnings' => new external_warnings(), 1160 ) 1161 ); 1162 } 1163 1164 /** 1165 * Describes the parameters for save_attempt. 1166 * 1167 * @return external_function_parameters 1168 * @since Moodle 3.1 1169 */ 1170 public static function save_attempt_parameters() { 1171 return new external_function_parameters ( 1172 array( 1173 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1174 'data' => new external_multiple_structure( 1175 new external_single_structure( 1176 array( 1177 'name' => new external_value(PARAM_RAW, 'data name'), 1178 'value' => new external_value(PARAM_RAW, 'data value'), 1179 ) 1180 ), 'the data to be saved' 1181 ), 1182 'preflightdata' => new external_multiple_structure( 1183 new external_single_structure( 1184 array( 1185 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1186 'value' => new external_value(PARAM_RAW, 'data value'), 1187 ) 1188 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1189 ) 1190 ) 1191 ); 1192 } 1193 1194 /** 1195 * Processes save requests during the quiz. This function is intended for the quiz auto-save feature. 1196 * 1197 * @param int $attemptid attempt id 1198 * @param array $data the data to be saved 1199 * @param array $preflightdata preflight required data (like passwords) 1200 * @return array of warnings and execution result 1201 * @since Moodle 3.1 1202 */ 1203 public static function save_attempt($attemptid, $data, $preflightdata = array()) { 1204 global $DB, $USER; 1205 1206 $warnings = array(); 1207 1208 $params = array( 1209 'attemptid' => $attemptid, 1210 'data' => $data, 1211 'preflightdata' => $preflightdata, 1212 ); 1213 $params = self::validate_parameters(self::save_attempt_parameters(), $params); 1214 1215 // Add a page, required by validate_attempt. 1216 list($attemptobj, $messages) = self::validate_attempt($params); 1217 1218 // Prevent functions like file_get_submitted_draft_itemid() or form library requiring a sesskey for WS requests. 1219 if (WS_SERVER || PHPUNIT_TEST) { 1220 $USER->ignoresesskey = true; 1221 } 1222 $transaction = $DB->start_delegated_transaction(); 1223 // Create the $_POST object required by the question engine. 1224 $_POST = array(); 1225 foreach ($data as $element) { 1226 $_POST[$element['name']] = $element['value']; 1227 // Some deep core functions like file_get_submitted_draft_itemid() also requires $_REQUEST to be filled. 1228 $_REQUEST[$element['name']] = $element['value']; 1229 } 1230 $timenow = time(); 1231 // Update the timemodifiedoffline field. 1232 $attemptobj->set_offline_modified_time($timenow); 1233 $attemptobj->process_auto_save($timenow); 1234 $transaction->allow_commit(); 1235 1236 $result = array(); 1237 $result['status'] = true; 1238 $result['warnings'] = $warnings; 1239 return $result; 1240 } 1241 1242 /** 1243 * Describes the save_attempt return value. 1244 * 1245 * @return external_single_structure 1246 * @since Moodle 3.1 1247 */ 1248 public static function save_attempt_returns() { 1249 return new external_single_structure( 1250 array( 1251 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1252 'warnings' => new external_warnings(), 1253 ) 1254 ); 1255 } 1256 1257 /** 1258 * Describes the parameters for process_attempt. 1259 * 1260 * @return external_function_parameters 1261 * @since Moodle 3.1 1262 */ 1263 public static function process_attempt_parameters() { 1264 return new external_function_parameters ( 1265 array( 1266 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1267 'data' => new external_multiple_structure( 1268 new external_single_structure( 1269 array( 1270 'name' => new external_value(PARAM_RAW, 'data name'), 1271 'value' => new external_value(PARAM_RAW, 'data value'), 1272 ) 1273 ), 1274 'the data to be saved', VALUE_DEFAULT, array() 1275 ), 1276 'finishattempt' => new external_value(PARAM_BOOL, 'whether to finish or not the attempt', VALUE_DEFAULT, false), 1277 'timeup' => new external_value(PARAM_BOOL, 'whether the WS was called by a timer when the time is up', 1278 VALUE_DEFAULT, false), 1279 'preflightdata' => new external_multiple_structure( 1280 new external_single_structure( 1281 array( 1282 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1283 'value' => new external_value(PARAM_RAW, 'data value'), 1284 ) 1285 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1286 ) 1287 ) 1288 ); 1289 } 1290 1291 /** 1292 * Process responses during an attempt at a quiz and also deals with attempts finishing. 1293 * 1294 * @param int $attemptid attempt id 1295 * @param array $data the data to be saved 1296 * @param bool $finishattempt whether to finish or not the attempt 1297 * @param bool $timeup whether the WS was called by a timer when the time is up 1298 * @param array $preflightdata preflight required data (like passwords) 1299 * @return array of warnings and the attempt state after the processing 1300 * @since Moodle 3.1 1301 */ 1302 public static function process_attempt($attemptid, $data, $finishattempt = false, $timeup = false, $preflightdata = array()) { 1303 global $USER; 1304 1305 $warnings = array(); 1306 1307 $params = array( 1308 'attemptid' => $attemptid, 1309 'data' => $data, 1310 'finishattempt' => $finishattempt, 1311 'timeup' => $timeup, 1312 'preflightdata' => $preflightdata, 1313 ); 1314 $params = self::validate_parameters(self::process_attempt_parameters(), $params); 1315 1316 // Do not check access manager rules and evaluate fail if overdue. 1317 $attemptobj = quiz_attempt::create($params['attemptid']); 1318 $failifoverdue = !($attemptobj->get_quizobj()->get_quiz()->overduehandling == 'graceperiod'); 1319 1320 list($attemptobj, $messages) = self::validate_attempt($params, false, $failifoverdue); 1321 1322 // Prevent functions like file_get_submitted_draft_itemid() or form library requiring a sesskey for WS requests. 1323 if (WS_SERVER || PHPUNIT_TEST) { 1324 $USER->ignoresesskey = true; 1325 } 1326 // Create the $_POST object required by the question engine. 1327 $_POST = array(); 1328 foreach ($params['data'] as $element) { 1329 $_POST[$element['name']] = $element['value']; 1330 $_REQUEST[$element['name']] = $element['value']; 1331 } 1332 $timenow = time(); 1333 $finishattempt = $params['finishattempt']; 1334 $timeup = $params['timeup']; 1335 1336 $result = array(); 1337 // Update the timemodifiedoffline field. 1338 $attemptobj->set_offline_modified_time($timenow); 1339 $result['state'] = $attemptobj->process_attempt($timenow, $finishattempt, $timeup, 0); 1340 1341 $result['warnings'] = $warnings; 1342 return $result; 1343 } 1344 1345 /** 1346 * Describes the process_attempt return value. 1347 * 1348 * @return external_single_structure 1349 * @since Moodle 3.1 1350 */ 1351 public static function process_attempt_returns() { 1352 return new external_single_structure( 1353 array( 1354 'state' => new external_value(PARAM_ALPHANUMEXT, 'state: the new attempt state: 1355 inprogress, finished, overdue, abandoned'), 1356 'warnings' => new external_warnings(), 1357 ) 1358 ); 1359 } 1360 1361 /** 1362 * Validate an attempt finished for review. The attempt would be reviewed by a user or a teacher. 1363 * 1364 * @param array $params Array of parameters including the attemptid 1365 * @return array containing the attempt object and display options 1366 * @since Moodle 3.1 1367 * @throws moodle_exception 1368 * @throws moodle_quiz_exception 1369 */ 1370 protected static function validate_attempt_review($params) { 1371 1372 $attemptobj = quiz_attempt::create($params['attemptid']); 1373 $attemptobj->check_review_capability(); 1374 1375 $displayoptions = $attemptobj->get_display_options(true); 1376 if ($attemptobj->is_own_attempt()) { 1377 if (!$attemptobj->is_finished()) { 1378 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed'); 1379 } else if (!$displayoptions->attempt) { 1380 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreview', null, '', 1381 $attemptobj->cannot_review_message()); 1382 } 1383 } else if (!$attemptobj->is_review_allowed()) { 1384 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt'); 1385 } 1386 return array($attemptobj, $displayoptions); 1387 } 1388 1389 /** 1390 * Describes the parameters for get_attempt_review. 1391 * 1392 * @return external_function_parameters 1393 * @since Moodle 3.1 1394 */ 1395 public static function get_attempt_review_parameters() { 1396 return new external_function_parameters ( 1397 array( 1398 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1399 'page' => new external_value(PARAM_INT, 'page number, empty for all the questions in all the pages', 1400 VALUE_DEFAULT, -1), 1401 ) 1402 ); 1403 } 1404 1405 /** 1406 * Returns review information for the given finished attempt, can be used by users or teachers. 1407 * 1408 * @param int $attemptid attempt id 1409 * @param int $page page number, empty for all the questions in all the pages 1410 * @return array of warnings and the attempt data, feedback and questions 1411 * @since Moodle 3.1 1412 * @throws moodle_exception 1413 * @throws moodle_quiz_exception 1414 */ 1415 public static function get_attempt_review($attemptid, $page = -1) { 1416 global $PAGE; 1417 1418 $warnings = array(); 1419 1420 $params = array( 1421 'attemptid' => $attemptid, 1422 'page' => $page, 1423 ); 1424 $params = self::validate_parameters(self::get_attempt_review_parameters(), $params); 1425 1426 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1427 1428 if ($params['page'] !== -1) { 1429 $page = $attemptobj->force_page_number_into_range($params['page']); 1430 } else { 1431 $page = 'all'; 1432 } 1433 1434 // Prepare the output. 1435 $result = array(); 1436 $result['attempt'] = $attemptobj->get_attempt(); 1437 $result['questions'] = self::get_attempt_questions_data($attemptobj, true, $page, true); 1438 1439 $result['additionaldata'] = array(); 1440 // Summary data (from behaviours). 1441 $summarydata = $attemptobj->get_additional_summary_data($displayoptions); 1442 foreach ($summarydata as $key => $data) { 1443 // This text does not need formatting (no need for external_format_[string|text]). 1444 $result['additionaldata'][] = array( 1445 'id' => $key, 1446 'title' => $data['title'], $attemptobj->get_quizobj()->get_context()->id, 1447 'content' => $data['content'], 1448 ); 1449 } 1450 1451 // Feedback if there is any, and the user is allowed to see it now. 1452 $grade = quiz_rescale_grade($attemptobj->get_attempt()->sumgrades, $attemptobj->get_quiz(), false); 1453 1454 $feedback = $attemptobj->get_overall_feedback($grade); 1455 if ($displayoptions->overallfeedback && $feedback) { 1456 $result['additionaldata'][] = array( 1457 'id' => 'feedback', 1458 'title' => get_string('feedback', 'quiz'), 1459 'content' => $feedback, 1460 ); 1461 } 1462 1463 $result['grade'] = $grade; 1464 $result['warnings'] = $warnings; 1465 return $result; 1466 } 1467 1468 /** 1469 * Describes the get_attempt_review return value. 1470 * 1471 * @return external_single_structure 1472 * @since Moodle 3.1 1473 */ 1474 public static function get_attempt_review_returns() { 1475 return new external_single_structure( 1476 array( 1477 'grade' => new external_value(PARAM_RAW, 'grade for the quiz (or empty or "notyetgraded")'), 1478 'attempt' => self::attempt_structure(), 1479 'additionaldata' => new external_multiple_structure( 1480 new external_single_structure( 1481 array( 1482 'id' => new external_value(PARAM_ALPHANUMEXT, 'id of the data'), 1483 'title' => new external_value(PARAM_TEXT, 'data title'), 1484 'content' => new external_value(PARAM_RAW, 'data content'), 1485 ) 1486 ) 1487 ), 1488 'questions' => new external_multiple_structure(self::question_structure()), 1489 'warnings' => new external_warnings(), 1490 ) 1491 ); 1492 } 1493 1494 /** 1495 * Describes the parameters for view_attempt. 1496 * 1497 * @return external_function_parameters 1498 * @since Moodle 3.1 1499 */ 1500 public static function view_attempt_parameters() { 1501 return new external_function_parameters ( 1502 array( 1503 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1504 'page' => new external_value(PARAM_INT, 'page number'), 1505 'preflightdata' => new external_multiple_structure( 1506 new external_single_structure( 1507 array( 1508 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1509 'value' => new external_value(PARAM_RAW, 'data value'), 1510 ) 1511 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1512 ) 1513 ) 1514 ); 1515 } 1516 1517 /** 1518 * Trigger the attempt viewed event. 1519 * 1520 * @param int $attemptid attempt id 1521 * @param int $page page number 1522 * @param array $preflightdata preflight required data (like passwords) 1523 * @return array of warnings and status result 1524 * @since Moodle 3.1 1525 */ 1526 public static function view_attempt($attemptid, $page, $preflightdata = array()) { 1527 1528 $warnings = array(); 1529 1530 $params = array( 1531 'attemptid' => $attemptid, 1532 'page' => $page, 1533 'preflightdata' => $preflightdata, 1534 ); 1535 $params = self::validate_parameters(self::view_attempt_parameters(), $params); 1536 list($attemptobj, $messages) = self::validate_attempt($params); 1537 1538 // Log action. 1539 $attemptobj->fire_attempt_viewed_event(); 1540 1541 // Update attempt page, throwing an exception if $page is not valid. 1542 if (!$attemptobj->set_currentpage($params['page'])) { 1543 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access'); 1544 } 1545 1546 $result = array(); 1547 $result['status'] = true; 1548 $result['warnings'] = $warnings; 1549 return $result; 1550 } 1551 1552 /** 1553 * Describes the view_attempt return value. 1554 * 1555 * @return external_single_structure 1556 * @since Moodle 3.1 1557 */ 1558 public static function view_attempt_returns() { 1559 return new external_single_structure( 1560 array( 1561 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1562 'warnings' => new external_warnings(), 1563 ) 1564 ); 1565 } 1566 1567 /** 1568 * Describes the parameters for view_attempt_summary. 1569 * 1570 * @return external_function_parameters 1571 * @since Moodle 3.1 1572 */ 1573 public static function view_attempt_summary_parameters() { 1574 return new external_function_parameters ( 1575 array( 1576 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1577 'preflightdata' => new external_multiple_structure( 1578 new external_single_structure( 1579 array( 1580 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1581 'value' => new external_value(PARAM_RAW, 'data value'), 1582 ) 1583 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1584 ) 1585 ) 1586 ); 1587 } 1588 1589 /** 1590 * Trigger the attempt summary viewed event. 1591 * 1592 * @param int $attemptid attempt id 1593 * @param array $preflightdata preflight required data (like passwords) 1594 * @return array of warnings and status result 1595 * @since Moodle 3.1 1596 */ 1597 public static function view_attempt_summary($attemptid, $preflightdata = array()) { 1598 1599 $warnings = array(); 1600 1601 $params = array( 1602 'attemptid' => $attemptid, 1603 'preflightdata' => $preflightdata, 1604 ); 1605 $params = self::validate_parameters(self::view_attempt_summary_parameters(), $params); 1606 list($attemptobj, $messages) = self::validate_attempt($params); 1607 1608 // Log action. 1609 $attemptobj->fire_attempt_summary_viewed_event(); 1610 1611 $result = array(); 1612 $result['status'] = true; 1613 $result['warnings'] = $warnings; 1614 return $result; 1615 } 1616 1617 /** 1618 * Describes the view_attempt_summary return value. 1619 * 1620 * @return external_single_structure 1621 * @since Moodle 3.1 1622 */ 1623 public static function view_attempt_summary_returns() { 1624 return new external_single_structure( 1625 array( 1626 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1627 'warnings' => new external_warnings(), 1628 ) 1629 ); 1630 } 1631 1632 /** 1633 * Describes the parameters for view_attempt_review. 1634 * 1635 * @return external_function_parameters 1636 * @since Moodle 3.1 1637 */ 1638 public static function view_attempt_review_parameters() { 1639 return new external_function_parameters ( 1640 array( 1641 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1642 ) 1643 ); 1644 } 1645 1646 /** 1647 * Trigger the attempt reviewed event. 1648 * 1649 * @param int $attemptid attempt id 1650 * @return array of warnings and status result 1651 * @since Moodle 3.1 1652 */ 1653 public static function view_attempt_review($attemptid) { 1654 1655 $warnings = array(); 1656 1657 $params = array( 1658 'attemptid' => $attemptid, 1659 ); 1660 $params = self::validate_parameters(self::view_attempt_review_parameters(), $params); 1661 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1662 1663 // Log action. 1664 $attemptobj->fire_attempt_reviewed_event(); 1665 1666 $result = array(); 1667 $result['status'] = true; 1668 $result['warnings'] = $warnings; 1669 return $result; 1670 } 1671 1672 /** 1673 * Describes the view_attempt_review return value. 1674 * 1675 * @return external_single_structure 1676 * @since Moodle 3.1 1677 */ 1678 public static function view_attempt_review_returns() { 1679 return new external_single_structure( 1680 array( 1681 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1682 'warnings' => new external_warnings(), 1683 ) 1684 ); 1685 } 1686 1687 /** 1688 * Describes the parameters for view_quiz. 1689 * 1690 * @return external_function_parameters 1691 * @since Moodle 3.1 1692 */ 1693 public static function get_quiz_feedback_for_grade_parameters() { 1694 return new external_function_parameters ( 1695 array( 1696 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1697 'grade' => new external_value(PARAM_FLOAT, 'the grade to check'), 1698 ) 1699 ); 1700 } 1701 1702 /** 1703 * Get the feedback text that should be show to a student who got the given grade in the given quiz. 1704 * 1705 * @param int $quizid quiz instance id 1706 * @param float $grade the grade to check 1707 * @return array of warnings and status result 1708 * @since Moodle 3.1 1709 * @throws moodle_exception 1710 */ 1711 public static function get_quiz_feedback_for_grade($quizid, $grade) { 1712 global $DB; 1713 1714 $params = array( 1715 'quizid' => $quizid, 1716 'grade' => $grade, 1717 ); 1718 $params = self::validate_parameters(self::get_quiz_feedback_for_grade_parameters(), $params); 1719 $warnings = array(); 1720 1721 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1722 1723 $result = array(); 1724 $result['feedbacktext'] = ''; 1725 $result['feedbacktextformat'] = FORMAT_MOODLE; 1726 1727 $feedback = quiz_feedback_record_for_grade($params['grade'], $quiz); 1728 if (!empty($feedback->feedbacktext)) { 1729 list($text, $format) = external_format_text($feedback->feedbacktext, $feedback->feedbacktextformat, $context->id, 1730 'mod_quiz', 'feedback', $feedback->id); 1731 $result['feedbacktext'] = $text; 1732 $result['feedbacktextformat'] = $format; 1733 $feedbackinlinefiles = external_util::get_area_files($context->id, 'mod_quiz', 'feedback', $feedback->id); 1734 if (!empty($feedbackinlinefiles)) { 1735 $result['feedbackinlinefiles'] = $feedbackinlinefiles; 1736 } 1737 } 1738 1739 $result['warnings'] = $warnings; 1740 return $result; 1741 } 1742 1743 /** 1744 * Describes the get_quiz_feedback_for_grade return value. 1745 * 1746 * @return external_single_structure 1747 * @since Moodle 3.1 1748 */ 1749 public static function get_quiz_feedback_for_grade_returns() { 1750 return new external_single_structure( 1751 array( 1752 'feedbacktext' => new external_value(PARAM_RAW, 'the comment that corresponds to this grade (empty for none)'), 1753 'feedbacktextformat' => new external_format_value('feedbacktext', VALUE_OPTIONAL), 1754 'feedbackinlinefiles' => new external_files('feedback inline files', VALUE_OPTIONAL), 1755 'warnings' => new external_warnings(), 1756 ) 1757 ); 1758 } 1759 1760 /** 1761 * Describes the parameters for get_quiz_access_information. 1762 * 1763 * @return external_function_parameters 1764 * @since Moodle 3.1 1765 */ 1766 public static function get_quiz_access_information_parameters() { 1767 return new external_function_parameters ( 1768 array( 1769 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1770 ) 1771 ); 1772 } 1773 1774 /** 1775 * Return access information for a given quiz. 1776 * 1777 * @param int $quizid quiz instance id 1778 * @return array of warnings and the access information 1779 * @since Moodle 3.1 1780 * @throws moodle_quiz_exception 1781 */ 1782 public static function get_quiz_access_information($quizid) { 1783 global $DB, $USER; 1784 1785 $warnings = array(); 1786 1787 $params = array( 1788 'quizid' => $quizid 1789 ); 1790 $params = self::validate_parameters(self::get_quiz_access_information_parameters(), $params); 1791 1792 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1793 1794 $result = array(); 1795 // Capabilities first. 1796 $result['canattempt'] = has_capability('mod/quiz:attempt', $context);; 1797 $result['canmanage'] = has_capability('mod/quiz:manage', $context);; 1798 $result['canpreview'] = has_capability('mod/quiz:preview', $context);; 1799 $result['canreviewmyattempts'] = has_capability('mod/quiz:reviewmyattempts', $context);; 1800 $result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);; 1801 1802 // Access manager now. 1803 $quizobj = quiz::create($cm->instance, $USER->id); 1804 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1805 $timenow = time(); 1806 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1807 1808 $result['accessrules'] = $accessmanager->describe_rules(); 1809 $result['activerulenames'] = $accessmanager->get_active_rule_names(); 1810 $result['preventaccessreasons'] = $accessmanager->prevent_access(); 1811 1812 $result['warnings'] = $warnings; 1813 return $result; 1814 } 1815 1816 /** 1817 * Describes the get_quiz_access_information return value. 1818 * 1819 * @return external_single_structure 1820 * @since Moodle 3.1 1821 */ 1822 public static function get_quiz_access_information_returns() { 1823 return new external_single_structure( 1824 array( 1825 'canattempt' => new external_value(PARAM_BOOL, 'Whether the user can do the quiz or not.'), 1826 'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can edit the quiz settings or not.'), 1827 'canpreview' => new external_value(PARAM_BOOL, 'Whether the user can preview the quiz or not.'), 1828 'canreviewmyattempts' => new external_value(PARAM_BOOL, 'Whether the users can review their previous attempts 1829 or not.'), 1830 'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the quiz reports or not.'), 1831 'accessrules' => new external_multiple_structure( 1832 new external_value(PARAM_TEXT, 'rule description'), 'list of rules'), 1833 'activerulenames' => new external_multiple_structure( 1834 new external_value(PARAM_PLUGIN, 'rule plugin names'), 'list of active rules'), 1835 'preventaccessreasons' => new external_multiple_structure( 1836 new external_value(PARAM_TEXT, 'access restriction description'), 'list of reasons'), 1837 'warnings' => new external_warnings(), 1838 ) 1839 ); 1840 } 1841 1842 /** 1843 * Describes the parameters for get_attempt_access_information. 1844 * 1845 * @return external_function_parameters 1846 * @since Moodle 3.1 1847 */ 1848 public static function get_attempt_access_information_parameters() { 1849 return new external_function_parameters ( 1850 array( 1851 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1852 'attemptid' => new external_value(PARAM_INT, 'attempt id, 0 for the user last attempt if exists', VALUE_DEFAULT, 0), 1853 ) 1854 ); 1855 } 1856 1857 /** 1858 * Return access information for a given attempt in a quiz. 1859 * 1860 * @param int $quizid quiz instance id 1861 * @param int $attemptid attempt id, 0 for the user last attempt if exists 1862 * @return array of warnings and the access information 1863 * @since Moodle 3.1 1864 * @throws moodle_quiz_exception 1865 */ 1866 public static function get_attempt_access_information($quizid, $attemptid = 0) { 1867 global $DB, $USER; 1868 1869 $warnings = array(); 1870 1871 $params = array( 1872 'quizid' => $quizid, 1873 'attemptid' => $attemptid, 1874 ); 1875 $params = self::validate_parameters(self::get_attempt_access_information_parameters(), $params); 1876 1877 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1878 1879 $attempttocheck = 0; 1880 if (!empty($params['attemptid'])) { 1881 $attemptobj = quiz_attempt::create($params['attemptid']); 1882 if ($attemptobj->get_userid() != $USER->id) { 1883 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); 1884 } 1885 $attempttocheck = $attemptobj->get_attempt(); 1886 } 1887 1888 // Access manager now. 1889 $quizobj = quiz::create($cm->instance, $USER->id); 1890 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1891 $timenow = time(); 1892 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1893 1894 $attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true); 1895 $lastfinishedattempt = end($attempts); 1896 if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { 1897 $attempts[] = $unfinishedattempt; 1898 1899 // Check if the attempt is now overdue. In that case the state will change. 1900 $quizobj->create_attempt_object($unfinishedattempt)->handle_if_time_expired(time(), false); 1901 1902 if ($unfinishedattempt->state != quiz_attempt::IN_PROGRESS and $unfinishedattempt->state != quiz_attempt::OVERDUE) { 1903 $lastfinishedattempt = $unfinishedattempt; 1904 } 1905 } 1906 $numattempts = count($attempts); 1907 1908 if (!$attempttocheck) { 1909 $attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt; 1910 } 1911 1912 $result = array(); 1913 $result['isfinished'] = $accessmanager->is_finished($numattempts, $lastfinishedattempt); 1914 $result['preventnewattemptreasons'] = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt); 1915 1916 if ($attempttocheck) { 1917 $endtime = $accessmanager->get_end_time($attempttocheck); 1918 $result['endtime'] = ($endtime === false) ? 0 : $endtime; 1919 $attemptid = $unfinishedattempt ? $unfinishedattempt->id : null; 1920 $result['ispreflightcheckrequired'] = $accessmanager->is_preflight_check_required($attemptid); 1921 } 1922 1923 $result['warnings'] = $warnings; 1924 return $result; 1925 } 1926 1927 /** 1928 * Describes the get_attempt_access_information return value. 1929 * 1930 * @return external_single_structure 1931 * @since Moodle 3.1 1932 */ 1933 public static function get_attempt_access_information_returns() { 1934 return new external_single_structure( 1935 array( 1936 'endtime' => new external_value(PARAM_INT, 'When the attempt must be submitted (determined by rules).', 1937 VALUE_OPTIONAL), 1938 'isfinished' => new external_value(PARAM_BOOL, 'Whether there is no way the user will ever be allowed to attempt.'), 1939 'ispreflightcheckrequired' => new external_value(PARAM_BOOL, 'whether a check is required before the user 1940 starts/continues his attempt.', VALUE_OPTIONAL), 1941 'preventnewattemptreasons' => new external_multiple_structure( 1942 new external_value(PARAM_TEXT, 'access restriction description'), 1943 'list of reasons'), 1944 'warnings' => new external_warnings(), 1945 ) 1946 ); 1947 } 1948 1949 /** 1950 * Describes the parameters for get_quiz_required_qtypes. 1951 * 1952 * @return external_function_parameters 1953 * @since Moodle 3.1 1954 */ 1955 public static function get_quiz_required_qtypes_parameters() { 1956 return new external_function_parameters ( 1957 array( 1958 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1959 ) 1960 ); 1961 } 1962 1963 /** 1964 * Return the potential question types that would be required for a given quiz. 1965 * Please note that for random question types we return the potential question types in the category choosen. 1966 * 1967 * @param int $quizid quiz instance id 1968 * @return array of warnings and the access information 1969 * @since Moodle 3.1 1970 * @throws moodle_quiz_exception 1971 */ 1972 public static function get_quiz_required_qtypes($quizid) { 1973 global $DB, $USER; 1974 1975 $warnings = array(); 1976 1977 $params = array( 1978 'quizid' => $quizid 1979 ); 1980 $params = self::validate_parameters(self::get_quiz_required_qtypes_parameters(), $params); 1981 1982 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1983 1984 $quizobj = quiz::create($cm->instance, $USER->id); 1985 $quizobj->preload_questions(); 1986 $quizobj->load_questions(); 1987 1988 // Question types used. 1989 $result = array(); 1990 $result['questiontypes'] = $quizobj->get_all_question_types_used(true); 1991 $result['warnings'] = $warnings; 1992 return $result; 1993 } 1994 1995 /** 1996 * Describes the get_quiz_required_qtypes return value. 1997 * 1998 * @return external_single_structure 1999 * @since Moodle 3.1 2000 */ 2001 public static function get_quiz_required_qtypes_returns() { 2002 return new external_single_structure( 2003 array( 2004 'questiontypes' => new external_multiple_structure( 2005 new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'), 2006 'warnings' => new external_warnings(), 2007 ) 2008 ); 2009 } 2010 2011 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body