Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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, $CFG; 510 require_once($CFG->libdir . '/gradelib.php'); 511 512 $warnings = array(); 513 514 $params = array( 515 'quizid' => $quizid, 516 'userid' => $userid, 517 ); 518 $params = self::validate_parameters(self::get_user_best_grade_parameters(), $params); 519 520 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 521 522 // Default value for userid. 523 if (empty($params['userid'])) { 524 $params['userid'] = $USER->id; 525 } 526 527 $user = core_user::get_user($params['userid'], '*', MUST_EXIST); 528 core_user::require_active_user($user); 529 530 // Extra checks so only users with permissions can view other users attempts. 531 if ($USER->id != $user->id) { 532 require_capability('mod/quiz:viewreports', $context); 533 } 534 535 $result = array(); 536 537 // This code was mostly copied from mod/quiz/view.php. We need to make the web service logic consistent. 538 // Get this user's attempts. 539 $attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all'); 540 $canviewgrade = false; 541 if ($attempts) { 542 if ($USER->id != $user->id) { 543 // No need to check the permission here. We did it at by require_capability('mod/quiz:viewreports', $context). 544 $canviewgrade = true; 545 } else { 546 // Work out which columns we need, taking account what data is available in each attempt. 547 [$notused, $alloptions] = quiz_get_combined_reviewoptions($quiz, $attempts); 548 $canviewgrade = $alloptions->marks >= question_display_options::MARK_AND_MAX; 549 } 550 } 551 552 $grade = $canviewgrade ? quiz_get_best_grade($quiz, $user->id) : null; 553 554 if ($grade === null) { 555 $result['hasgrade'] = false; 556 } else { 557 $result['hasgrade'] = true; 558 $result['grade'] = $grade; 559 } 560 561 // Inform user of the grade to pass if non-zero. 562 $gradinginfo = grade_get_grades($course->id, 'mod', 'quiz', $quiz->id, $user->id); 563 if (!empty($gradinginfo->items)) { 564 $item = $gradinginfo->items[0]; 565 566 if ($item && grade_floats_different($item->gradepass, 0)) { 567 $result['gradetopass'] = $item->gradepass; 568 } 569 } 570 571 $result['warnings'] = $warnings; 572 return $result; 573 } 574 575 /** 576 * Describes the get_user_best_grade return value. 577 * 578 * @return external_single_structure 579 * @since Moodle 3.1 580 */ 581 public static function get_user_best_grade_returns() { 582 return new external_single_structure( 583 array( 584 'hasgrade' => new external_value(PARAM_BOOL, 'Whether the user has a grade on the given quiz.'), 585 'grade' => new external_value(PARAM_FLOAT, 'The grade (only if the user has a grade).', VALUE_OPTIONAL), 586 'gradetopass' => new external_value(PARAM_FLOAT, 'The grade to pass the quiz (only if set).', VALUE_OPTIONAL), 587 'warnings' => new external_warnings(), 588 ) 589 ); 590 } 591 592 /** 593 * Describes the parameters for get_combined_review_options. 594 * 595 * @return external_function_parameters 596 * @since Moodle 3.1 597 */ 598 public static function get_combined_review_options_parameters() { 599 return new external_function_parameters ( 600 array( 601 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 602 'userid' => new external_value(PARAM_INT, 'user id (empty for current user)', VALUE_DEFAULT, 0), 603 604 ) 605 ); 606 } 607 608 /** 609 * Combines the review options from a number of different quiz attempts. 610 * 611 * @param int $quizid quiz instance id 612 * @param int $userid user id (empty for current user) 613 * @return array of warnings and the review options 614 * @since Moodle 3.1 615 */ 616 public static function get_combined_review_options($quizid, $userid = 0) { 617 global $DB, $USER; 618 619 $warnings = array(); 620 621 $params = array( 622 'quizid' => $quizid, 623 'userid' => $userid, 624 ); 625 $params = self::validate_parameters(self::get_combined_review_options_parameters(), $params); 626 627 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 628 629 // Default value for userid. 630 if (empty($params['userid'])) { 631 $params['userid'] = $USER->id; 632 } 633 634 $user = core_user::get_user($params['userid'], '*', MUST_EXIST); 635 core_user::require_active_user($user); 636 637 // Extra checks so only users with permissions can view other users attempts. 638 if ($USER->id != $user->id) { 639 require_capability('mod/quiz:viewreports', $context); 640 } 641 642 $attempts = quiz_get_user_attempts($quiz->id, $user->id, 'all', true); 643 644 $result = array(); 645 $result['someoptions'] = []; 646 $result['alloptions'] = []; 647 648 list($someoptions, $alloptions) = quiz_get_combined_reviewoptions($quiz, $attempts); 649 650 foreach (array('someoptions', 'alloptions') as $typeofoption) { 651 foreach ($$typeofoption as $key => $value) { 652 $result[$typeofoption][] = array( 653 "name" => $key, 654 "value" => (!empty($value)) ? $value : 0 655 ); 656 } 657 } 658 659 $result['warnings'] = $warnings; 660 return $result; 661 } 662 663 /** 664 * Describes the get_combined_review_options return value. 665 * 666 * @return external_single_structure 667 * @since Moodle 3.1 668 */ 669 public static function get_combined_review_options_returns() { 670 return new external_single_structure( 671 array( 672 'someoptions' => new external_multiple_structure( 673 new external_single_structure( 674 array( 675 'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), 676 'value' => new external_value(PARAM_INT, 'option value'), 677 ) 678 ) 679 ), 680 'alloptions' => new external_multiple_structure( 681 new external_single_structure( 682 array( 683 'name' => new external_value(PARAM_ALPHANUMEXT, 'option name'), 684 'value' => new external_value(PARAM_INT, 'option value'), 685 ) 686 ) 687 ), 688 'warnings' => new external_warnings(), 689 ) 690 ); 691 } 692 693 /** 694 * Describes the parameters for start_attempt. 695 * 696 * @return external_function_parameters 697 * @since Moodle 3.1 698 */ 699 public static function start_attempt_parameters() { 700 return new external_function_parameters ( 701 array( 702 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 703 'preflightdata' => new external_multiple_structure( 704 new external_single_structure( 705 array( 706 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 707 'value' => new external_value(PARAM_RAW, 'data value'), 708 ) 709 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 710 ), 711 'forcenew' => new external_value(PARAM_BOOL, 'Whether to force a new attempt or not.', VALUE_DEFAULT, false), 712 713 ) 714 ); 715 } 716 717 /** 718 * Starts a new attempt at a quiz. 719 * 720 * @param int $quizid quiz instance id 721 * @param array $preflightdata preflight required data (like passwords) 722 * @param bool $forcenew Whether to force a new attempt or not. 723 * @return array of warnings and the attempt basic data 724 * @since Moodle 3.1 725 * @throws moodle_quiz_exception 726 */ 727 public static function start_attempt($quizid, $preflightdata = array(), $forcenew = false) { 728 global $DB, $USER; 729 730 $warnings = array(); 731 $attempt = array(); 732 733 $params = array( 734 'quizid' => $quizid, 735 'preflightdata' => $preflightdata, 736 'forcenew' => $forcenew, 737 ); 738 $params = self::validate_parameters(self::start_attempt_parameters(), $params); 739 $forcenew = $params['forcenew']; 740 741 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 742 743 $quizobj = quiz::create($cm->instance, $USER->id); 744 745 // Check questions. 746 if (!$quizobj->has_questions()) { 747 throw new moodle_quiz_exception($quizobj, 'noquestionsfound'); 748 } 749 750 // Create an object to manage all the other (non-roles) access rules. 751 $timenow = time(); 752 $accessmanager = $quizobj->get_access_manager($timenow); 753 754 // Validate permissions for creating a new attempt and start a new preview attempt if required. 755 list($currentattemptid, $attemptnumber, $lastattempt, $messages, $page) = 756 quiz_validate_new_attempt($quizobj, $accessmanager, $forcenew, -1, false); 757 758 // Check access. 759 if (!$quizobj->is_preview_user() && $messages) { 760 // Create warnings with the exact messages. 761 foreach ($messages as $message) { 762 $warnings[] = array( 763 'item' => 'quiz', 764 'itemid' => $quiz->id, 765 'warningcode' => '1', 766 'message' => clean_text($message, PARAM_TEXT) 767 ); 768 } 769 } else { 770 if ($accessmanager->is_preflight_check_required($currentattemptid)) { 771 // Need to do some checks before allowing the user to continue. 772 773 $provideddata = array(); 774 foreach ($params['preflightdata'] as $data) { 775 $provideddata[$data['name']] = $data['value']; 776 } 777 778 $errors = $accessmanager->validate_preflight_check($provideddata, [], $currentattemptid); 779 780 if (!empty($errors)) { 781 throw new moodle_quiz_exception($quizobj, array_shift($errors)); 782 } 783 784 // Pre-flight check passed. 785 $accessmanager->notify_preflight_check_passed($currentattemptid); 786 } 787 788 if ($currentattemptid) { 789 if ($lastattempt->state == quiz_attempt::OVERDUE) { 790 throw new moodle_quiz_exception($quizobj, 'stateoverdue'); 791 } else { 792 throw new moodle_quiz_exception($quizobj, 'attemptstillinprogress'); 793 } 794 } 795 $offlineattempt = WS_SERVER ? true : false; 796 $attempt = quiz_prepare_and_start_new_attempt($quizobj, $attemptnumber, $lastattempt, $offlineattempt); 797 } 798 799 $result = array(); 800 $result['attempt'] = $attempt; 801 $result['warnings'] = $warnings; 802 return $result; 803 } 804 805 /** 806 * Describes the start_attempt return value. 807 * 808 * @return external_single_structure 809 * @since Moodle 3.1 810 */ 811 public static function start_attempt_returns() { 812 return new external_single_structure( 813 array( 814 'attempt' => self::attempt_structure(), 815 'warnings' => new external_warnings(), 816 ) 817 ); 818 } 819 820 /** 821 * Utility function for validating a given attempt 822 * 823 * @param array $params array of parameters including the attemptid and preflight data 824 * @param bool $checkaccessrules whether to check the quiz access rules or not 825 * @param bool $failifoverdue whether to return error if the attempt is overdue 826 * @return array containing the attempt object and access messages 827 * @throws moodle_quiz_exception 828 * @since Moodle 3.1 829 */ 830 protected static function validate_attempt($params, $checkaccessrules = true, $failifoverdue = true) { 831 global $USER; 832 833 $attemptobj = quiz_attempt::create($params['attemptid']); 834 835 $context = context_module::instance($attemptobj->get_cm()->id); 836 self::validate_context($context); 837 838 // Check that this attempt belongs to this user. 839 if ($attemptobj->get_userid() != $USER->id) { 840 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); 841 } 842 843 // General capabilities check. 844 $ispreviewuser = $attemptobj->is_preview_user(); 845 if (!$ispreviewuser) { 846 $attemptobj->require_capability('mod/quiz:attempt'); 847 } 848 849 // Check the access rules. 850 $accessmanager = $attemptobj->get_access_manager(time()); 851 $messages = array(); 852 if ($checkaccessrules) { 853 // If the attempt is now overdue, or abandoned, deal with that. 854 $attemptobj->handle_if_time_expired(time(), true); 855 856 $messages = $accessmanager->prevent_access(); 857 if (!$ispreviewuser && $messages) { 858 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attempterror'); 859 } 860 } 861 862 // Attempt closed?. 863 if ($attemptobj->is_finished()) { 864 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptalreadyclosed'); 865 } else if ($failifoverdue && $attemptobj->get_state() == quiz_attempt::OVERDUE) { 866 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'stateoverdue'); 867 } 868 869 // User submitted data (like the quiz password). 870 if ($accessmanager->is_preflight_check_required($attemptobj->get_attemptid())) { 871 $provideddata = array(); 872 foreach ($params['preflightdata'] as $data) { 873 $provideddata[$data['name']] = $data['value']; 874 } 875 876 $errors = $accessmanager->validate_preflight_check($provideddata, [], $params['attemptid']); 877 if (!empty($errors)) { 878 throw new moodle_quiz_exception($attemptobj->get_quizobj(), array_shift($errors)); 879 } 880 // Pre-flight check passed. 881 $accessmanager->notify_preflight_check_passed($params['attemptid']); 882 } 883 884 if (isset($params['page'])) { 885 // Check if the page is out of range. 886 if ($params['page'] != $attemptobj->force_page_number_into_range($params['page'])) { 887 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Invalid page number'); 888 } 889 890 // Prevent out of sequence access. 891 if (!$attemptobj->check_page_access($params['page'])) { 892 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access'); 893 } 894 895 // Check slots. 896 $slots = $attemptobj->get_slots($params['page']); 897 898 if (empty($slots)) { 899 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noquestionsfound'); 900 } 901 } 902 903 return array($attemptobj, $messages); 904 } 905 906 /** 907 * Describes a single question structure. 908 * 909 * @return external_single_structure the question data. Some fields may not be returned depending on the quiz display settings. 910 * @since Moodle 3.1 911 * @since Moodle 3.2 blockedbyprevious parameter added. 912 */ 913 private static function question_structure() { 914 return new external_single_structure( 915 array( 916 'slot' => new external_value(PARAM_INT, 'slot number'), 917 'type' => new external_value(PARAM_ALPHANUMEXT, 'question type, i.e: multichoice'), 918 'page' => new external_value(PARAM_INT, 'page of the quiz this question appears on'), 919 'html' => new external_value(PARAM_RAW, 'the question rendered'), 920 'responsefileareas' => new external_multiple_structure( 921 new external_single_structure( 922 array( 923 'area' => new external_value(PARAM_NOTAGS, 'File area name'), 924 'files' => new external_files('Response files for the question', VALUE_OPTIONAL), 925 ) 926 ), 'Response file areas including files', VALUE_OPTIONAL 927 ), 928 'sequencecheck' => new external_value(PARAM_INT, 'the number of real steps in this attempt', VALUE_OPTIONAL), 929 'lastactiontime' => new external_value(PARAM_INT, 'the timestamp of the most recent step in this question attempt', 930 VALUE_OPTIONAL), 931 'hasautosavedstep' => new external_value(PARAM_BOOL, 'whether this question attempt has autosaved data', 932 VALUE_OPTIONAL), 933 'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'), 934 'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL), 935 'state' => new external_value(PARAM_ALPHA, 'the state where the question is in. 936 It will not be returned if the user cannot see it due to the quiz display correctness settings.', 937 VALUE_OPTIONAL), 938 'status' => new external_value(PARAM_RAW, 'current formatted state of the question', VALUE_OPTIONAL), 939 'blockedbyprevious' => new external_value(PARAM_BOOL, 'whether the question is blocked by the previous question', 940 VALUE_OPTIONAL), 941 'mark' => new external_value(PARAM_RAW, 'the mark awarded. 942 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 943 'maxmark' => new external_value(PARAM_FLOAT, 'the maximum mark possible for this question attempt. 944 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 945 'settings' => new external_value(PARAM_RAW, 'Question settings (JSON encoded).', VALUE_OPTIONAL), 946 ), 947 'The question data. Some fields may not be returned depending on the quiz display settings.' 948 ); 949 } 950 951 /** 952 * Return questions information for a given attempt. 953 * 954 * @param quiz_attempt $attemptobj the quiz attempt object 955 * @param bool $review whether if we are in review mode or not 956 * @param mixed $page string 'all' or integer page number 957 * @return array array of questions including data 958 */ 959 private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all') { 960 global $PAGE; 961 962 $questions = array(); 963 $contextid = $attemptobj->get_quizobj()->get_context()->id; 964 $displayoptions = $attemptobj->get_display_options($review); 965 $renderer = $PAGE->get_renderer('mod_quiz'); 966 $contextid = $attemptobj->get_quizobj()->get_context()->id; 967 968 foreach ($attemptobj->get_slots($page) as $slot) { 969 $qtype = $attemptobj->get_question_type_name($slot); 970 $qattempt = $attemptobj->get_question_attempt($slot); 971 $questiondef = $qattempt->get_question(true); 972 973 // Get response files (for questions like essay that allows attachments). 974 $responsefileareas = []; 975 foreach (question_bank::get_qtype($qtype)->response_file_areas() as $area) { 976 if ($files = $attemptobj->get_question_attempt($slot)->get_last_qt_files($area, $contextid)) { 977 $responsefileareas[$area]['area'] = $area; 978 $responsefileareas[$area]['files'] = []; 979 980 foreach ($files as $file) { 981 $responsefileareas[$area]['files'][] = array( 982 'filename' => $file->get_filename(), 983 'fileurl' => $qattempt->get_response_file_url($file), 984 'filesize' => $file->get_filesize(), 985 'filepath' => $file->get_filepath(), 986 'mimetype' => $file->get_mimetype(), 987 'timemodified' => $file->get_timemodified(), 988 ); 989 } 990 } 991 } 992 993 // Check display settings for question. 994 $settings = $questiondef->get_question_definition_for_external_rendering($qattempt, $displayoptions); 995 996 $question = array( 997 'slot' => $slot, 998 'type' => $qtype, 999 'page' => $attemptobj->get_question_page($slot), 1000 'flagged' => $attemptobj->is_question_flagged($slot), 1001 'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code(), 1002 'responsefileareas' => $responsefileareas, 1003 'sequencecheck' => $qattempt->get_sequence_check_count(), 1004 'lastactiontime' => $qattempt->get_last_step()->get_timecreated(), 1005 'hasautosavedstep' => $qattempt->has_autosaved_step(), 1006 'settings' => !empty($settings) ? json_encode($settings) : null, 1007 ); 1008 1009 if ($attemptobj->is_real_question($slot)) { 1010 $question['number'] = $attemptobj->get_question_number($slot); 1011 $showcorrectness = $displayoptions->correctness && $qattempt->has_marks(); 1012 if ($showcorrectness) { 1013 $question['state'] = (string) $attemptobj->get_question_state($slot); 1014 } 1015 $question['status'] = $attemptobj->get_question_status($slot, $displayoptions->correctness); 1016 $question['blockedbyprevious'] = $attemptobj->is_blocked_by_previous_question($slot); 1017 } 1018 if ($displayoptions->marks >= question_display_options::MAX_ONLY) { 1019 $question['maxmark'] = $qattempt->get_max_mark(); 1020 } 1021 if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) { 1022 $question['mark'] = $attemptobj->get_question_mark($slot); 1023 } 1024 if ($attemptobj->check_page_access($attemptobj->get_question_page($slot), false)) { 1025 $questions[] = $question; 1026 } 1027 } 1028 return $questions; 1029 } 1030 1031 /** 1032 * Describes the parameters for get_attempt_data. 1033 * 1034 * @return external_function_parameters 1035 * @since Moodle 3.1 1036 */ 1037 public static function get_attempt_data_parameters() { 1038 return new external_function_parameters ( 1039 array( 1040 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1041 'page' => new external_value(PARAM_INT, 'page number'), 1042 'preflightdata' => new external_multiple_structure( 1043 new external_single_structure( 1044 array( 1045 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1046 'value' => new external_value(PARAM_RAW, 'data value'), 1047 ) 1048 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1049 ) 1050 ) 1051 ); 1052 } 1053 1054 /** 1055 * Returns information for the given attempt page for a quiz attempt in progress. 1056 * 1057 * @param int $attemptid attempt id 1058 * @param int $page page number 1059 * @param array $preflightdata preflight required data (like passwords) 1060 * @return array of warnings and the attempt data, next page, message and questions 1061 * @since Moodle 3.1 1062 * @throws moodle_quiz_exceptions 1063 */ 1064 public static function get_attempt_data($attemptid, $page, $preflightdata = array()) { 1065 1066 $warnings = array(); 1067 1068 $params = array( 1069 'attemptid' => $attemptid, 1070 'page' => $page, 1071 'preflightdata' => $preflightdata, 1072 ); 1073 $params = self::validate_parameters(self::get_attempt_data_parameters(), $params); 1074 1075 list($attemptobj, $messages) = self::validate_attempt($params); 1076 1077 if ($attemptobj->is_last_page($params['page'])) { 1078 $nextpage = -1; 1079 } else { 1080 $nextpage = $params['page'] + 1; 1081 } 1082 1083 $result = array(); 1084 $result['attempt'] = $attemptobj->get_attempt(); 1085 $result['messages'] = $messages; 1086 $result['nextpage'] = $nextpage; 1087 $result['warnings'] = $warnings; 1088 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, $params['page']); 1089 1090 return $result; 1091 } 1092 1093 /** 1094 * Describes the get_attempt_data return value. 1095 * 1096 * @return external_single_structure 1097 * @since Moodle 3.1 1098 */ 1099 public static function get_attempt_data_returns() { 1100 return new external_single_structure( 1101 array( 1102 'attempt' => self::attempt_structure(), 1103 'messages' => new external_multiple_structure( 1104 new external_value(PARAM_TEXT, 'access message'), 1105 'access messages, will only be returned for users with mod/quiz:preview capability, 1106 for other users this method will throw an exception if there are messages'), 1107 'nextpage' => new external_value(PARAM_INT, 'next page number'), 1108 'questions' => new external_multiple_structure(self::question_structure()), 1109 'warnings' => new external_warnings(), 1110 ) 1111 ); 1112 } 1113 1114 /** 1115 * Describes the parameters for get_attempt_summary. 1116 * 1117 * @return external_function_parameters 1118 * @since Moodle 3.1 1119 */ 1120 public static function get_attempt_summary_parameters() { 1121 return new external_function_parameters ( 1122 array( 1123 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1124 'preflightdata' => new external_multiple_structure( 1125 new external_single_structure( 1126 array( 1127 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1128 'value' => new external_value(PARAM_RAW, 'data value'), 1129 ) 1130 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1131 ) 1132 ) 1133 ); 1134 } 1135 1136 /** 1137 * Returns a summary of a quiz attempt before it is submitted. 1138 * 1139 * @param int $attemptid attempt id 1140 * @param int $preflightdata preflight required data (like passwords) 1141 * @return array of warnings and the attempt summary data for each question 1142 * @since Moodle 3.1 1143 */ 1144 public static function get_attempt_summary($attemptid, $preflightdata = array()) { 1145 1146 $warnings = array(); 1147 1148 $params = array( 1149 'attemptid' => $attemptid, 1150 'preflightdata' => $preflightdata, 1151 ); 1152 $params = self::validate_parameters(self::get_attempt_summary_parameters(), $params); 1153 1154 list($attemptobj, $messages) = self::validate_attempt($params, true, false); 1155 1156 $result = array(); 1157 $result['warnings'] = $warnings; 1158 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all'); 1159 1160 return $result; 1161 } 1162 1163 /** 1164 * Describes the get_attempt_summary return value. 1165 * 1166 * @return external_single_structure 1167 * @since Moodle 3.1 1168 */ 1169 public static function get_attempt_summary_returns() { 1170 return new external_single_structure( 1171 array( 1172 'questions' => new external_multiple_structure(self::question_structure()), 1173 'warnings' => new external_warnings(), 1174 ) 1175 ); 1176 } 1177 1178 /** 1179 * Describes the parameters for save_attempt. 1180 * 1181 * @return external_function_parameters 1182 * @since Moodle 3.1 1183 */ 1184 public static function save_attempt_parameters() { 1185 return new external_function_parameters ( 1186 array( 1187 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1188 'data' => new external_multiple_structure( 1189 new external_single_structure( 1190 array( 1191 'name' => new external_value(PARAM_RAW, 'data name'), 1192 'value' => new external_value(PARAM_RAW, 'data value'), 1193 ) 1194 ), 'the data to be saved' 1195 ), 1196 'preflightdata' => new external_multiple_structure( 1197 new external_single_structure( 1198 array( 1199 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1200 'value' => new external_value(PARAM_RAW, 'data value'), 1201 ) 1202 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1203 ) 1204 ) 1205 ); 1206 } 1207 1208 /** 1209 * Processes save requests during the quiz. This function is intended for the quiz auto-save feature. 1210 * 1211 * @param int $attemptid attempt id 1212 * @param array $data the data to be saved 1213 * @param array $preflightdata preflight required data (like passwords) 1214 * @return array of warnings and execution result 1215 * @since Moodle 3.1 1216 */ 1217 public static function save_attempt($attemptid, $data, $preflightdata = array()) { 1218 global $DB, $USER; 1219 1220 $warnings = array(); 1221 1222 $params = array( 1223 'attemptid' => $attemptid, 1224 'data' => $data, 1225 'preflightdata' => $preflightdata, 1226 ); 1227 $params = self::validate_parameters(self::save_attempt_parameters(), $params); 1228 1229 // Add a page, required by validate_attempt. 1230 list($attemptobj, $messages) = self::validate_attempt($params); 1231 1232 // Prevent functions like file_get_submitted_draft_itemid() or form library requiring a sesskey for WS requests. 1233 if (WS_SERVER || PHPUNIT_TEST) { 1234 $USER->ignoresesskey = true; 1235 } 1236 $transaction = $DB->start_delegated_transaction(); 1237 // Create the $_POST object required by the question engine. 1238 $_POST = array(); 1239 foreach ($data as $element) { 1240 $_POST[$element['name']] = $element['value']; 1241 // Some deep core functions like file_get_submitted_draft_itemid() also requires $_REQUEST to be filled. 1242 $_REQUEST[$element['name']] = $element['value']; 1243 } 1244 $timenow = time(); 1245 // Update the timemodifiedoffline field. 1246 $attemptobj->set_offline_modified_time($timenow); 1247 $attemptobj->process_auto_save($timenow); 1248 $transaction->allow_commit(); 1249 1250 $result = array(); 1251 $result['status'] = true; 1252 $result['warnings'] = $warnings; 1253 return $result; 1254 } 1255 1256 /** 1257 * Describes the save_attempt return value. 1258 * 1259 * @return external_single_structure 1260 * @since Moodle 3.1 1261 */ 1262 public static function save_attempt_returns() { 1263 return new external_single_structure( 1264 array( 1265 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1266 'warnings' => new external_warnings(), 1267 ) 1268 ); 1269 } 1270 1271 /** 1272 * Describes the parameters for process_attempt. 1273 * 1274 * @return external_function_parameters 1275 * @since Moodle 3.1 1276 */ 1277 public static function process_attempt_parameters() { 1278 return new external_function_parameters ( 1279 array( 1280 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1281 'data' => new external_multiple_structure( 1282 new external_single_structure( 1283 array( 1284 'name' => new external_value(PARAM_RAW, 'data name'), 1285 'value' => new external_value(PARAM_RAW, 'data value'), 1286 ) 1287 ), 1288 'the data to be saved', VALUE_DEFAULT, array() 1289 ), 1290 'finishattempt' => new external_value(PARAM_BOOL, 'whether to finish or not the attempt', VALUE_DEFAULT, false), 1291 'timeup' => new external_value(PARAM_BOOL, 'whether the WS was called by a timer when the time is up', 1292 VALUE_DEFAULT, false), 1293 'preflightdata' => new external_multiple_structure( 1294 new external_single_structure( 1295 array( 1296 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1297 'value' => new external_value(PARAM_RAW, 'data value'), 1298 ) 1299 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1300 ) 1301 ) 1302 ); 1303 } 1304 1305 /** 1306 * Process responses during an attempt at a quiz and also deals with attempts finishing. 1307 * 1308 * @param int $attemptid attempt id 1309 * @param array $data the data to be saved 1310 * @param bool $finishattempt whether to finish or not the attempt 1311 * @param bool $timeup whether the WS was called by a timer when the time is up 1312 * @param array $preflightdata preflight required data (like passwords) 1313 * @return array of warnings and the attempt state after the processing 1314 * @since Moodle 3.1 1315 */ 1316 public static function process_attempt($attemptid, $data, $finishattempt = false, $timeup = false, $preflightdata = array()) { 1317 global $USER; 1318 1319 $warnings = array(); 1320 1321 $params = array( 1322 'attemptid' => $attemptid, 1323 'data' => $data, 1324 'finishattempt' => $finishattempt, 1325 'timeup' => $timeup, 1326 'preflightdata' => $preflightdata, 1327 ); 1328 $params = self::validate_parameters(self::process_attempt_parameters(), $params); 1329 1330 // Do not check access manager rules and evaluate fail if overdue. 1331 $attemptobj = quiz_attempt::create($params['attemptid']); 1332 $failifoverdue = !($attemptobj->get_quizobj()->get_quiz()->overduehandling == 'graceperiod'); 1333 1334 list($attemptobj, $messages) = self::validate_attempt($params, false, $failifoverdue); 1335 1336 // Prevent functions like file_get_submitted_draft_itemid() or form library requiring a sesskey for WS requests. 1337 if (WS_SERVER || PHPUNIT_TEST) { 1338 $USER->ignoresesskey = true; 1339 } 1340 // Create the $_POST object required by the question engine. 1341 $_POST = array(); 1342 foreach ($params['data'] as $element) { 1343 $_POST[$element['name']] = $element['value']; 1344 $_REQUEST[$element['name']] = $element['value']; 1345 } 1346 $timenow = time(); 1347 $finishattempt = $params['finishattempt']; 1348 $timeup = $params['timeup']; 1349 1350 $result = array(); 1351 // Update the timemodifiedoffline field. 1352 $attemptobj->set_offline_modified_time($timenow); 1353 $result['state'] = $attemptobj->process_attempt($timenow, $finishattempt, $timeup, 0); 1354 1355 $result['warnings'] = $warnings; 1356 return $result; 1357 } 1358 1359 /** 1360 * Describes the process_attempt return value. 1361 * 1362 * @return external_single_structure 1363 * @since Moodle 3.1 1364 */ 1365 public static function process_attempt_returns() { 1366 return new external_single_structure( 1367 array( 1368 'state' => new external_value(PARAM_ALPHANUMEXT, 'state: the new attempt state: 1369 inprogress, finished, overdue, abandoned'), 1370 'warnings' => new external_warnings(), 1371 ) 1372 ); 1373 } 1374 1375 /** 1376 * Validate an attempt finished for review. The attempt would be reviewed by a user or a teacher. 1377 * 1378 * @param array $params Array of parameters including the attemptid 1379 * @return array containing the attempt object and display options 1380 * @since Moodle 3.1 1381 * @throws moodle_exception 1382 * @throws moodle_quiz_exception 1383 */ 1384 protected static function validate_attempt_review($params) { 1385 1386 $attemptobj = quiz_attempt::create($params['attemptid']); 1387 $attemptobj->check_review_capability(); 1388 1389 $displayoptions = $attemptobj->get_display_options(true); 1390 if ($attemptobj->is_own_attempt()) { 1391 if (!$attemptobj->is_finished()) { 1392 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed'); 1393 } else if (!$displayoptions->attempt) { 1394 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreview', null, '', 1395 $attemptobj->cannot_review_message()); 1396 } 1397 } else if (!$attemptobj->is_review_allowed()) { 1398 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt'); 1399 } 1400 return array($attemptobj, $displayoptions); 1401 } 1402 1403 /** 1404 * Describes the parameters for get_attempt_review. 1405 * 1406 * @return external_function_parameters 1407 * @since Moodle 3.1 1408 */ 1409 public static function get_attempt_review_parameters() { 1410 return new external_function_parameters ( 1411 array( 1412 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1413 'page' => new external_value(PARAM_INT, 'page number, empty for all the questions in all the pages', 1414 VALUE_DEFAULT, -1), 1415 ) 1416 ); 1417 } 1418 1419 /** 1420 * Returns review information for the given finished attempt, can be used by users or teachers. 1421 * 1422 * @param int $attemptid attempt id 1423 * @param int $page page number, empty for all the questions in all the pages 1424 * @return array of warnings and the attempt data, feedback and questions 1425 * @since Moodle 3.1 1426 * @throws moodle_exception 1427 * @throws moodle_quiz_exception 1428 */ 1429 public static function get_attempt_review($attemptid, $page = -1) { 1430 global $PAGE; 1431 1432 $warnings = array(); 1433 1434 $params = array( 1435 'attemptid' => $attemptid, 1436 'page' => $page, 1437 ); 1438 $params = self::validate_parameters(self::get_attempt_review_parameters(), $params); 1439 1440 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1441 1442 if ($params['page'] !== -1) { 1443 $page = $attemptobj->force_page_number_into_range($params['page']); 1444 } else { 1445 $page = 'all'; 1446 } 1447 1448 // Prepare the output. 1449 $result = array(); 1450 $result['attempt'] = $attemptobj->get_attempt(); 1451 $result['questions'] = self::get_attempt_questions_data($attemptobj, true, $page, true); 1452 1453 $result['additionaldata'] = array(); 1454 // Summary data (from behaviours). 1455 $summarydata = $attemptobj->get_additional_summary_data($displayoptions); 1456 foreach ($summarydata as $key => $data) { 1457 // This text does not need formatting (no need for external_format_[string|text]). 1458 $result['additionaldata'][] = array( 1459 'id' => $key, 1460 'title' => $data['title'], $attemptobj->get_quizobj()->get_context()->id, 1461 'content' => $data['content'], 1462 ); 1463 } 1464 1465 // Feedback if there is any, and the user is allowed to see it now. 1466 $grade = quiz_rescale_grade($attemptobj->get_attempt()->sumgrades, $attemptobj->get_quiz(), false); 1467 1468 $feedback = $attemptobj->get_overall_feedback($grade); 1469 if ($displayoptions->overallfeedback && $feedback) { 1470 $result['additionaldata'][] = array( 1471 'id' => 'feedback', 1472 'title' => get_string('feedback', 'quiz'), 1473 'content' => $feedback, 1474 ); 1475 } 1476 1477 $result['grade'] = $grade; 1478 $result['warnings'] = $warnings; 1479 return $result; 1480 } 1481 1482 /** 1483 * Describes the get_attempt_review return value. 1484 * 1485 * @return external_single_structure 1486 * @since Moodle 3.1 1487 */ 1488 public static function get_attempt_review_returns() { 1489 return new external_single_structure( 1490 array( 1491 'grade' => new external_value(PARAM_RAW, 'grade for the quiz (or empty or "notyetgraded")'), 1492 'attempt' => self::attempt_structure(), 1493 'additionaldata' => new external_multiple_structure( 1494 new external_single_structure( 1495 array( 1496 'id' => new external_value(PARAM_ALPHANUMEXT, 'id of the data'), 1497 'title' => new external_value(PARAM_TEXT, 'data title'), 1498 'content' => new external_value(PARAM_RAW, 'data content'), 1499 ) 1500 ) 1501 ), 1502 'questions' => new external_multiple_structure(self::question_structure()), 1503 'warnings' => new external_warnings(), 1504 ) 1505 ); 1506 } 1507 1508 /** 1509 * Describes the parameters for view_attempt. 1510 * 1511 * @return external_function_parameters 1512 * @since Moodle 3.1 1513 */ 1514 public static function view_attempt_parameters() { 1515 return new external_function_parameters ( 1516 array( 1517 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1518 'page' => new external_value(PARAM_INT, 'page number'), 1519 'preflightdata' => new external_multiple_structure( 1520 new external_single_structure( 1521 array( 1522 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1523 'value' => new external_value(PARAM_RAW, 'data value'), 1524 ) 1525 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1526 ) 1527 ) 1528 ); 1529 } 1530 1531 /** 1532 * Trigger the attempt viewed event. 1533 * 1534 * @param int $attemptid attempt id 1535 * @param int $page page number 1536 * @param array $preflightdata preflight required data (like passwords) 1537 * @return array of warnings and status result 1538 * @since Moodle 3.1 1539 */ 1540 public static function view_attempt($attemptid, $page, $preflightdata = array()) { 1541 1542 $warnings = array(); 1543 1544 $params = array( 1545 'attemptid' => $attemptid, 1546 'page' => $page, 1547 'preflightdata' => $preflightdata, 1548 ); 1549 $params = self::validate_parameters(self::view_attempt_parameters(), $params); 1550 list($attemptobj, $messages) = self::validate_attempt($params); 1551 1552 // Log action. 1553 $attemptobj->fire_attempt_viewed_event(); 1554 1555 // Update attempt page, throwing an exception if $page is not valid. 1556 if (!$attemptobj->set_currentpage($params['page'])) { 1557 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access'); 1558 } 1559 1560 $result = array(); 1561 $result['status'] = true; 1562 $result['warnings'] = $warnings; 1563 return $result; 1564 } 1565 1566 /** 1567 * Describes the view_attempt return value. 1568 * 1569 * @return external_single_structure 1570 * @since Moodle 3.1 1571 */ 1572 public static function view_attempt_returns() { 1573 return new external_single_structure( 1574 array( 1575 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1576 'warnings' => new external_warnings(), 1577 ) 1578 ); 1579 } 1580 1581 /** 1582 * Describes the parameters for view_attempt_summary. 1583 * 1584 * @return external_function_parameters 1585 * @since Moodle 3.1 1586 */ 1587 public static function view_attempt_summary_parameters() { 1588 return new external_function_parameters ( 1589 array( 1590 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1591 'preflightdata' => new external_multiple_structure( 1592 new external_single_structure( 1593 array( 1594 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1595 'value' => new external_value(PARAM_RAW, 'data value'), 1596 ) 1597 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1598 ) 1599 ) 1600 ); 1601 } 1602 1603 /** 1604 * Trigger the attempt summary viewed event. 1605 * 1606 * @param int $attemptid attempt id 1607 * @param array $preflightdata preflight required data (like passwords) 1608 * @return array of warnings and status result 1609 * @since Moodle 3.1 1610 */ 1611 public static function view_attempt_summary($attemptid, $preflightdata = array()) { 1612 1613 $warnings = array(); 1614 1615 $params = array( 1616 'attemptid' => $attemptid, 1617 'preflightdata' => $preflightdata, 1618 ); 1619 $params = self::validate_parameters(self::view_attempt_summary_parameters(), $params); 1620 list($attemptobj, $messages) = self::validate_attempt($params); 1621 1622 // Log action. 1623 $attemptobj->fire_attempt_summary_viewed_event(); 1624 1625 $result = array(); 1626 $result['status'] = true; 1627 $result['warnings'] = $warnings; 1628 return $result; 1629 } 1630 1631 /** 1632 * Describes the view_attempt_summary return value. 1633 * 1634 * @return external_single_structure 1635 * @since Moodle 3.1 1636 */ 1637 public static function view_attempt_summary_returns() { 1638 return new external_single_structure( 1639 array( 1640 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1641 'warnings' => new external_warnings(), 1642 ) 1643 ); 1644 } 1645 1646 /** 1647 * Describes the parameters for view_attempt_review. 1648 * 1649 * @return external_function_parameters 1650 * @since Moodle 3.1 1651 */ 1652 public static function view_attempt_review_parameters() { 1653 return new external_function_parameters ( 1654 array( 1655 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1656 ) 1657 ); 1658 } 1659 1660 /** 1661 * Trigger the attempt reviewed event. 1662 * 1663 * @param int $attemptid attempt id 1664 * @return array of warnings and status result 1665 * @since Moodle 3.1 1666 */ 1667 public static function view_attempt_review($attemptid) { 1668 1669 $warnings = array(); 1670 1671 $params = array( 1672 'attemptid' => $attemptid, 1673 ); 1674 $params = self::validate_parameters(self::view_attempt_review_parameters(), $params); 1675 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1676 1677 // Log action. 1678 $attemptobj->fire_attempt_reviewed_event(); 1679 1680 $result = array(); 1681 $result['status'] = true; 1682 $result['warnings'] = $warnings; 1683 return $result; 1684 } 1685 1686 /** 1687 * Describes the view_attempt_review return value. 1688 * 1689 * @return external_single_structure 1690 * @since Moodle 3.1 1691 */ 1692 public static function view_attempt_review_returns() { 1693 return new external_single_structure( 1694 array( 1695 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1696 'warnings' => new external_warnings(), 1697 ) 1698 ); 1699 } 1700 1701 /** 1702 * Describes the parameters for view_quiz. 1703 * 1704 * @return external_function_parameters 1705 * @since Moodle 3.1 1706 */ 1707 public static function get_quiz_feedback_for_grade_parameters() { 1708 return new external_function_parameters ( 1709 array( 1710 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1711 'grade' => new external_value(PARAM_FLOAT, 'the grade to check'), 1712 ) 1713 ); 1714 } 1715 1716 /** 1717 * Get the feedback text that should be show to a student who got the given grade in the given quiz. 1718 * 1719 * @param int $quizid quiz instance id 1720 * @param float $grade the grade to check 1721 * @return array of warnings and status result 1722 * @since Moodle 3.1 1723 * @throws moodle_exception 1724 */ 1725 public static function get_quiz_feedback_for_grade($quizid, $grade) { 1726 global $DB; 1727 1728 $params = array( 1729 'quizid' => $quizid, 1730 'grade' => $grade, 1731 ); 1732 $params = self::validate_parameters(self::get_quiz_feedback_for_grade_parameters(), $params); 1733 $warnings = array(); 1734 1735 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1736 1737 $result = array(); 1738 $result['feedbacktext'] = ''; 1739 $result['feedbacktextformat'] = FORMAT_MOODLE; 1740 1741 $feedback = quiz_feedback_record_for_grade($params['grade'], $quiz); 1742 if (!empty($feedback->feedbacktext)) { 1743 list($text, $format) = external_format_text($feedback->feedbacktext, $feedback->feedbacktextformat, $context->id, 1744 'mod_quiz', 'feedback', $feedback->id); 1745 $result['feedbacktext'] = $text; 1746 $result['feedbacktextformat'] = $format; 1747 $feedbackinlinefiles = external_util::get_area_files($context->id, 'mod_quiz', 'feedback', $feedback->id); 1748 if (!empty($feedbackinlinefiles)) { 1749 $result['feedbackinlinefiles'] = $feedbackinlinefiles; 1750 } 1751 } 1752 1753 $result['warnings'] = $warnings; 1754 return $result; 1755 } 1756 1757 /** 1758 * Describes the get_quiz_feedback_for_grade return value. 1759 * 1760 * @return external_single_structure 1761 * @since Moodle 3.1 1762 */ 1763 public static function get_quiz_feedback_for_grade_returns() { 1764 return new external_single_structure( 1765 array( 1766 'feedbacktext' => new external_value(PARAM_RAW, 'the comment that corresponds to this grade (empty for none)'), 1767 'feedbacktextformat' => new external_format_value('feedbacktext', VALUE_OPTIONAL), 1768 'feedbackinlinefiles' => new external_files('feedback inline files', VALUE_OPTIONAL), 1769 'warnings' => new external_warnings(), 1770 ) 1771 ); 1772 } 1773 1774 /** 1775 * Describes the parameters for get_quiz_access_information. 1776 * 1777 * @return external_function_parameters 1778 * @since Moodle 3.1 1779 */ 1780 public static function get_quiz_access_information_parameters() { 1781 return new external_function_parameters ( 1782 array( 1783 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1784 ) 1785 ); 1786 } 1787 1788 /** 1789 * Return access information for a given quiz. 1790 * 1791 * @param int $quizid quiz instance id 1792 * @return array of warnings and the access information 1793 * @since Moodle 3.1 1794 * @throws moodle_quiz_exception 1795 */ 1796 public static function get_quiz_access_information($quizid) { 1797 global $DB, $USER; 1798 1799 $warnings = array(); 1800 1801 $params = array( 1802 'quizid' => $quizid 1803 ); 1804 $params = self::validate_parameters(self::get_quiz_access_information_parameters(), $params); 1805 1806 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1807 1808 $result = array(); 1809 // Capabilities first. 1810 $result['canattempt'] = has_capability('mod/quiz:attempt', $context);; 1811 $result['canmanage'] = has_capability('mod/quiz:manage', $context);; 1812 $result['canpreview'] = has_capability('mod/quiz:preview', $context);; 1813 $result['canreviewmyattempts'] = has_capability('mod/quiz:reviewmyattempts', $context);; 1814 $result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);; 1815 1816 // Access manager now. 1817 $quizobj = quiz::create($cm->instance, $USER->id); 1818 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1819 $timenow = time(); 1820 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1821 1822 $result['accessrules'] = $accessmanager->describe_rules(); 1823 $result['activerulenames'] = $accessmanager->get_active_rule_names(); 1824 $result['preventaccessreasons'] = $accessmanager->prevent_access(); 1825 1826 $result['warnings'] = $warnings; 1827 return $result; 1828 } 1829 1830 /** 1831 * Describes the get_quiz_access_information return value. 1832 * 1833 * @return external_single_structure 1834 * @since Moodle 3.1 1835 */ 1836 public static function get_quiz_access_information_returns() { 1837 return new external_single_structure( 1838 array( 1839 'canattempt' => new external_value(PARAM_BOOL, 'Whether the user can do the quiz or not.'), 1840 'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can edit the quiz settings or not.'), 1841 'canpreview' => new external_value(PARAM_BOOL, 'Whether the user can preview the quiz or not.'), 1842 'canreviewmyattempts' => new external_value(PARAM_BOOL, 'Whether the users can review their previous attempts 1843 or not.'), 1844 'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the quiz reports or not.'), 1845 'accessrules' => new external_multiple_structure( 1846 new external_value(PARAM_TEXT, 'rule description'), 'list of rules'), 1847 'activerulenames' => new external_multiple_structure( 1848 new external_value(PARAM_PLUGIN, 'rule plugin names'), 'list of active rules'), 1849 'preventaccessreasons' => new external_multiple_structure( 1850 new external_value(PARAM_TEXT, 'access restriction description'), 'list of reasons'), 1851 'warnings' => new external_warnings(), 1852 ) 1853 ); 1854 } 1855 1856 /** 1857 * Describes the parameters for get_attempt_access_information. 1858 * 1859 * @return external_function_parameters 1860 * @since Moodle 3.1 1861 */ 1862 public static function get_attempt_access_information_parameters() { 1863 return new external_function_parameters ( 1864 array( 1865 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1866 'attemptid' => new external_value(PARAM_INT, 'attempt id, 0 for the user last attempt if exists', VALUE_DEFAULT, 0), 1867 ) 1868 ); 1869 } 1870 1871 /** 1872 * Return access information for a given attempt in a quiz. 1873 * 1874 * @param int $quizid quiz instance id 1875 * @param int $attemptid attempt id, 0 for the user last attempt if exists 1876 * @return array of warnings and the access information 1877 * @since Moodle 3.1 1878 * @throws moodle_quiz_exception 1879 */ 1880 public static function get_attempt_access_information($quizid, $attemptid = 0) { 1881 global $DB, $USER; 1882 1883 $warnings = array(); 1884 1885 $params = array( 1886 'quizid' => $quizid, 1887 'attemptid' => $attemptid, 1888 ); 1889 $params = self::validate_parameters(self::get_attempt_access_information_parameters(), $params); 1890 1891 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1892 1893 $attempttocheck = 0; 1894 if (!empty($params['attemptid'])) { 1895 $attemptobj = quiz_attempt::create($params['attemptid']); 1896 if ($attemptobj->get_userid() != $USER->id) { 1897 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); 1898 } 1899 $attempttocheck = $attemptobj->get_attempt(); 1900 } 1901 1902 // Access manager now. 1903 $quizobj = quiz::create($cm->instance, $USER->id); 1904 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1905 $timenow = time(); 1906 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1907 1908 $attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true); 1909 $lastfinishedattempt = end($attempts); 1910 if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { 1911 $attempts[] = $unfinishedattempt; 1912 1913 // Check if the attempt is now overdue. In that case the state will change. 1914 $quizobj->create_attempt_object($unfinishedattempt)->handle_if_time_expired(time(), false); 1915 1916 if ($unfinishedattempt->state != quiz_attempt::IN_PROGRESS and $unfinishedattempt->state != quiz_attempt::OVERDUE) { 1917 $lastfinishedattempt = $unfinishedattempt; 1918 } 1919 } 1920 $numattempts = count($attempts); 1921 1922 if (!$attempttocheck) { 1923 $attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt; 1924 } 1925 1926 $result = array(); 1927 $result['isfinished'] = $accessmanager->is_finished($numattempts, $lastfinishedattempt); 1928 $result['preventnewattemptreasons'] = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt); 1929 1930 if ($attempttocheck) { 1931 $endtime = $accessmanager->get_end_time($attempttocheck); 1932 $result['endtime'] = ($endtime === false) ? 0 : $endtime; 1933 $attemptid = $unfinishedattempt ? $unfinishedattempt->id : null; 1934 $result['ispreflightcheckrequired'] = $accessmanager->is_preflight_check_required($attemptid); 1935 } 1936 1937 $result['warnings'] = $warnings; 1938 return $result; 1939 } 1940 1941 /** 1942 * Describes the get_attempt_access_information return value. 1943 * 1944 * @return external_single_structure 1945 * @since Moodle 3.1 1946 */ 1947 public static function get_attempt_access_information_returns() { 1948 return new external_single_structure( 1949 array( 1950 'endtime' => new external_value(PARAM_INT, 'When the attempt must be submitted (determined by rules).', 1951 VALUE_OPTIONAL), 1952 'isfinished' => new external_value(PARAM_BOOL, 'Whether there is no way the user will ever be allowed to attempt.'), 1953 'ispreflightcheckrequired' => new external_value(PARAM_BOOL, 'whether a check is required before the user 1954 starts/continues his attempt.', VALUE_OPTIONAL), 1955 'preventnewattemptreasons' => new external_multiple_structure( 1956 new external_value(PARAM_TEXT, 'access restriction description'), 1957 'list of reasons'), 1958 'warnings' => new external_warnings(), 1959 ) 1960 ); 1961 } 1962 1963 /** 1964 * Describes the parameters for get_quiz_required_qtypes. 1965 * 1966 * @return external_function_parameters 1967 * @since Moodle 3.1 1968 */ 1969 public static function get_quiz_required_qtypes_parameters() { 1970 return new external_function_parameters ( 1971 array( 1972 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1973 ) 1974 ); 1975 } 1976 1977 /** 1978 * Return the potential question types that would be required for a given quiz. 1979 * Please note that for random question types we return the potential question types in the category choosen. 1980 * 1981 * @param int $quizid quiz instance id 1982 * @return array of warnings and the access information 1983 * @since Moodle 3.1 1984 * @throws moodle_quiz_exception 1985 */ 1986 public static function get_quiz_required_qtypes($quizid) { 1987 global $DB, $USER; 1988 1989 $warnings = array(); 1990 1991 $params = array( 1992 'quizid' => $quizid 1993 ); 1994 $params = self::validate_parameters(self::get_quiz_required_qtypes_parameters(), $params); 1995 1996 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1997 1998 $quizobj = quiz::create($cm->instance, $USER->id); 1999 $quizobj->preload_questions(); 2000 $quizobj->load_questions(); 2001 2002 // Question types used. 2003 $result = array(); 2004 $result['questiontypes'] = $quizobj->get_all_question_types_used(true); 2005 $result['warnings'] = $warnings; 2006 return $result; 2007 } 2008 2009 /** 2010 * Describes the get_quiz_required_qtypes return value. 2011 * 2012 * @return external_single_structure 2013 * @since Moodle 3.1 2014 */ 2015 public static function get_quiz_required_qtypes_returns() { 2016 return new external_single_structure( 2017 array( 2018 'questiontypes' => new external_multiple_structure( 2019 new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'), 2020 'warnings' => new external_warnings(), 2021 ) 2022 ); 2023 } 2024 2025 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body