See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 310] [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 'sequencecheck' => new external_value(PARAM_INT, 'the number of real steps in this attempt', VALUE_OPTIONAL), 908 'lastactiontime' => new external_value(PARAM_INT, 'the timestamp of the most recent step in this question attempt', 909 VALUE_OPTIONAL), 910 'hasautosavedstep' => new external_value(PARAM_BOOL, 'whether this question attempt has autosaved data', 911 VALUE_OPTIONAL), 912 'flagged' => new external_value(PARAM_BOOL, 'whether the question is flagged or not'), 913 'number' => new external_value(PARAM_INT, 'question ordering number in the quiz', VALUE_OPTIONAL), 914 'state' => new external_value(PARAM_ALPHA, 'the state where the question is in. 915 It will not be returned if the user cannot see it due to the quiz display correctness settings.', 916 VALUE_OPTIONAL), 917 'status' => new external_value(PARAM_RAW, 'current formatted state of the question', VALUE_OPTIONAL), 918 'blockedbyprevious' => new external_value(PARAM_BOOL, 'whether the question is blocked by the previous question', 919 VALUE_OPTIONAL), 920 'mark' => new external_value(PARAM_RAW, 'the mark awarded. 921 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 922 'maxmark' => new external_value(PARAM_FLOAT, 'the maximum mark possible for this question attempt. 923 It will be returned only if the user is allowed to see it.', VALUE_OPTIONAL), 924 ), 925 'The question data. Some fields may not be returned depending on the quiz display settings.' 926 ); 927 } 928 929 /** 930 * Return questions information for a given attempt. 931 * 932 * @param quiz_attempt $attemptobj the quiz attempt object 933 * @param bool $review whether if we are in review mode or not 934 * @param mixed $page string 'all' or integer page number 935 * @return array array of questions including data 936 */ 937 private static function get_attempt_questions_data(quiz_attempt $attemptobj, $review, $page = 'all') { 938 global $PAGE; 939 940 $questions = array(); 941 $contextid = $attemptobj->get_quizobj()->get_context()->id; 942 $displayoptions = $attemptobj->get_display_options($review); 943 $renderer = $PAGE->get_renderer('mod_quiz'); 944 945 foreach ($attemptobj->get_slots($page) as $slot) { 946 947 $question = array( 948 'slot' => $slot, 949 'type' => $attemptobj->get_question_type_name($slot), 950 'page' => $attemptobj->get_question_page($slot), 951 'flagged' => $attemptobj->is_question_flagged($slot), 952 'html' => $attemptobj->render_question($slot, $review, $renderer) . $PAGE->requires->get_end_code(), 953 'sequencecheck' => $attemptobj->get_question_attempt($slot)->get_sequence_check_count(), 954 'lastactiontime' => $attemptobj->get_question_attempt($slot)->get_last_step()->get_timecreated(), 955 'hasautosavedstep' => $attemptobj->get_question_attempt($slot)->has_autosaved_step() 956 ); 957 958 if ($attemptobj->is_real_question($slot)) { 959 $question['number'] = $attemptobj->get_question_number($slot); 960 $showcorrectness = $displayoptions->correctness && $attemptobj->get_question_attempt($slot)->has_marks(); 961 if ($showcorrectness) { 962 $question['state'] = (string) $attemptobj->get_question_state($slot); 963 } 964 $question['status'] = $attemptobj->get_question_status($slot, $displayoptions->correctness); 965 $question['blockedbyprevious'] = $attemptobj->is_blocked_by_previous_question($slot); 966 } 967 if ($displayoptions->marks >= question_display_options::MAX_ONLY) { 968 $question['maxmark'] = $attemptobj->get_question_attempt($slot)->get_max_mark(); 969 } 970 if ($displayoptions->marks >= question_display_options::MARK_AND_MAX) { 971 $question['mark'] = $attemptobj->get_question_mark($slot); 972 } 973 if ($attemptobj->check_page_access($attemptobj->get_question_page($slot), false)) { 974 $questions[] = $question; 975 } 976 } 977 return $questions; 978 } 979 980 /** 981 * Describes the parameters for get_attempt_data. 982 * 983 * @return external_function_parameters 984 * @since Moodle 3.1 985 */ 986 public static function get_attempt_data_parameters() { 987 return new external_function_parameters ( 988 array( 989 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 990 'page' => new external_value(PARAM_INT, 'page number'), 991 'preflightdata' => new external_multiple_structure( 992 new external_single_structure( 993 array( 994 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 995 'value' => new external_value(PARAM_RAW, 'data value'), 996 ) 997 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 998 ) 999 ) 1000 ); 1001 } 1002 1003 /** 1004 * Returns information for the given attempt page for a quiz attempt in progress. 1005 * 1006 * @param int $attemptid attempt id 1007 * @param int $page page number 1008 * @param array $preflightdata preflight required data (like passwords) 1009 * @return array of warnings and the attempt data, next page, message and questions 1010 * @since Moodle 3.1 1011 * @throws moodle_quiz_exceptions 1012 */ 1013 public static function get_attempt_data($attemptid, $page, $preflightdata = array()) { 1014 1015 $warnings = array(); 1016 1017 $params = array( 1018 'attemptid' => $attemptid, 1019 'page' => $page, 1020 'preflightdata' => $preflightdata, 1021 ); 1022 $params = self::validate_parameters(self::get_attempt_data_parameters(), $params); 1023 1024 list($attemptobj, $messages) = self::validate_attempt($params); 1025 1026 if ($attemptobj->is_last_page($params['page'])) { 1027 $nextpage = -1; 1028 } else { 1029 $nextpage = $params['page'] + 1; 1030 } 1031 1032 $result = array(); 1033 $result['attempt'] = $attemptobj->get_attempt(); 1034 $result['messages'] = $messages; 1035 $result['nextpage'] = $nextpage; 1036 $result['warnings'] = $warnings; 1037 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, $params['page']); 1038 1039 return $result; 1040 } 1041 1042 /** 1043 * Describes the get_attempt_data return value. 1044 * 1045 * @return external_single_structure 1046 * @since Moodle 3.1 1047 */ 1048 public static function get_attempt_data_returns() { 1049 return new external_single_structure( 1050 array( 1051 'attempt' => self::attempt_structure(), 1052 'messages' => new external_multiple_structure( 1053 new external_value(PARAM_TEXT, 'access message'), 1054 'access messages, will only be returned for users with mod/quiz:preview capability, 1055 for other users this method will throw an exception if there are messages'), 1056 'nextpage' => new external_value(PARAM_INT, 'next page number'), 1057 'questions' => new external_multiple_structure(self::question_structure()), 1058 'warnings' => new external_warnings(), 1059 ) 1060 ); 1061 } 1062 1063 /** 1064 * Describes the parameters for get_attempt_summary. 1065 * 1066 * @return external_function_parameters 1067 * @since Moodle 3.1 1068 */ 1069 public static function get_attempt_summary_parameters() { 1070 return new external_function_parameters ( 1071 array( 1072 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1073 'preflightdata' => new external_multiple_structure( 1074 new external_single_structure( 1075 array( 1076 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1077 'value' => new external_value(PARAM_RAW, 'data value'), 1078 ) 1079 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1080 ) 1081 ) 1082 ); 1083 } 1084 1085 /** 1086 * Returns a summary of a quiz attempt before it is submitted. 1087 * 1088 * @param int $attemptid attempt id 1089 * @param int $preflightdata preflight required data (like passwords) 1090 * @return array of warnings and the attempt summary data for each question 1091 * @since Moodle 3.1 1092 */ 1093 public static function get_attempt_summary($attemptid, $preflightdata = array()) { 1094 1095 $warnings = array(); 1096 1097 $params = array( 1098 'attemptid' => $attemptid, 1099 'preflightdata' => $preflightdata, 1100 ); 1101 $params = self::validate_parameters(self::get_attempt_summary_parameters(), $params); 1102 1103 list($attemptobj, $messages) = self::validate_attempt($params, true, false); 1104 1105 $result = array(); 1106 $result['warnings'] = $warnings; 1107 $result['questions'] = self::get_attempt_questions_data($attemptobj, false, 'all'); 1108 1109 return $result; 1110 } 1111 1112 /** 1113 * Describes the get_attempt_summary return value. 1114 * 1115 * @return external_single_structure 1116 * @since Moodle 3.1 1117 */ 1118 public static function get_attempt_summary_returns() { 1119 return new external_single_structure( 1120 array( 1121 'questions' => new external_multiple_structure(self::question_structure()), 1122 'warnings' => new external_warnings(), 1123 ) 1124 ); 1125 } 1126 1127 /** 1128 * Describes the parameters for save_attempt. 1129 * 1130 * @return external_function_parameters 1131 * @since Moodle 3.1 1132 */ 1133 public static function save_attempt_parameters() { 1134 return new external_function_parameters ( 1135 array( 1136 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1137 'data' => new external_multiple_structure( 1138 new external_single_structure( 1139 array( 1140 'name' => new external_value(PARAM_RAW, 'data name'), 1141 'value' => new external_value(PARAM_RAW, 'data value'), 1142 ) 1143 ), 'the data to be saved' 1144 ), 1145 'preflightdata' => new external_multiple_structure( 1146 new external_single_structure( 1147 array( 1148 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1149 'value' => new external_value(PARAM_RAW, 'data value'), 1150 ) 1151 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1152 ) 1153 ) 1154 ); 1155 } 1156 1157 /** 1158 * Processes save requests during the quiz. This function is intended for the quiz auto-save feature. 1159 * 1160 * @param int $attemptid attempt id 1161 * @param array $data the data to be saved 1162 * @param array $preflightdata preflight required data (like passwords) 1163 * @return array of warnings and execution result 1164 * @since Moodle 3.1 1165 */ 1166 public static function save_attempt($attemptid, $data, $preflightdata = array()) { 1167 global $DB; 1168 1169 $warnings = array(); 1170 1171 $params = array( 1172 'attemptid' => $attemptid, 1173 'data' => $data, 1174 'preflightdata' => $preflightdata, 1175 ); 1176 $params = self::validate_parameters(self::save_attempt_parameters(), $params); 1177 1178 // Add a page, required by validate_attempt. 1179 list($attemptobj, $messages) = self::validate_attempt($params); 1180 1181 $transaction = $DB->start_delegated_transaction(); 1182 // Create the $_POST object required by the question engine. 1183 $_POST = array(); 1184 foreach ($data as $element) { 1185 $_POST[$element['name']] = $element['value']; 1186 } 1187 $timenow = time(); 1188 // Update the timemodifiedoffline field. 1189 $attemptobj->set_offline_modified_time($timenow); 1190 $attemptobj->process_auto_save($timenow); 1191 $transaction->allow_commit(); 1192 1193 $result = array(); 1194 $result['status'] = true; 1195 $result['warnings'] = $warnings; 1196 return $result; 1197 } 1198 1199 /** 1200 * Describes the save_attempt return value. 1201 * 1202 * @return external_single_structure 1203 * @since Moodle 3.1 1204 */ 1205 public static function save_attempt_returns() { 1206 return new external_single_structure( 1207 array( 1208 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1209 'warnings' => new external_warnings(), 1210 ) 1211 ); 1212 } 1213 1214 /** 1215 * Describes the parameters for process_attempt. 1216 * 1217 * @return external_function_parameters 1218 * @since Moodle 3.1 1219 */ 1220 public static function process_attempt_parameters() { 1221 return new external_function_parameters ( 1222 array( 1223 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1224 'data' => new external_multiple_structure( 1225 new external_single_structure( 1226 array( 1227 'name' => new external_value(PARAM_RAW, 'data name'), 1228 'value' => new external_value(PARAM_RAW, 'data value'), 1229 ) 1230 ), 1231 'the data to be saved', VALUE_DEFAULT, array() 1232 ), 1233 'finishattempt' => new external_value(PARAM_BOOL, 'whether to finish or not the attempt', VALUE_DEFAULT, false), 1234 'timeup' => new external_value(PARAM_BOOL, 'whether the WS was called by a timer when the time is up', 1235 VALUE_DEFAULT, false), 1236 'preflightdata' => new external_multiple_structure( 1237 new external_single_structure( 1238 array( 1239 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1240 'value' => new external_value(PARAM_RAW, 'data value'), 1241 ) 1242 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1243 ) 1244 ) 1245 ); 1246 } 1247 1248 /** 1249 * Process responses during an attempt at a quiz and also deals with attempts finishing. 1250 * 1251 * @param int $attemptid attempt id 1252 * @param array $data the data to be saved 1253 * @param bool $finishattempt whether to finish or not the attempt 1254 * @param bool $timeup whether the WS was called by a timer when the time is up 1255 * @param array $preflightdata preflight required data (like passwords) 1256 * @return array of warnings and the attempt state after the processing 1257 * @since Moodle 3.1 1258 */ 1259 public static function process_attempt($attemptid, $data, $finishattempt = false, $timeup = false, $preflightdata = array()) { 1260 1261 $warnings = array(); 1262 1263 $params = array( 1264 'attemptid' => $attemptid, 1265 'data' => $data, 1266 'finishattempt' => $finishattempt, 1267 'timeup' => $timeup, 1268 'preflightdata' => $preflightdata, 1269 ); 1270 $params = self::validate_parameters(self::process_attempt_parameters(), $params); 1271 1272 // Do not check access manager rules and evaluate fail if overdue. 1273 $attemptobj = quiz_attempt::create($params['attemptid']); 1274 $failifoverdue = !($attemptobj->get_quizobj()->get_quiz()->overduehandling == 'graceperiod'); 1275 1276 list($attemptobj, $messages) = self::validate_attempt($params, false, $failifoverdue); 1277 1278 // Create the $_POST object required by the question engine. 1279 $_POST = array(); 1280 foreach ($params['data'] as $element) { 1281 $_POST[$element['name']] = $element['value']; 1282 } 1283 $timenow = time(); 1284 $finishattempt = $params['finishattempt']; 1285 $timeup = $params['timeup']; 1286 1287 $result = array(); 1288 // Update the timemodifiedoffline field. 1289 $attemptobj->set_offline_modified_time($timenow); 1290 $result['state'] = $attemptobj->process_attempt($timenow, $finishattempt, $timeup, 0); 1291 1292 $result['warnings'] = $warnings; 1293 return $result; 1294 } 1295 1296 /** 1297 * Describes the process_attempt return value. 1298 * 1299 * @return external_single_structure 1300 * @since Moodle 3.1 1301 */ 1302 public static function process_attempt_returns() { 1303 return new external_single_structure( 1304 array( 1305 'state' => new external_value(PARAM_ALPHANUMEXT, 'state: the new attempt state: 1306 inprogress, finished, overdue, abandoned'), 1307 'warnings' => new external_warnings(), 1308 ) 1309 ); 1310 } 1311 1312 /** 1313 * Validate an attempt finished for review. The attempt would be reviewed by a user or a teacher. 1314 * 1315 * @param array $params Array of parameters including the attemptid 1316 * @return array containing the attempt object and display options 1317 * @since Moodle 3.1 1318 * @throws moodle_exception 1319 * @throws moodle_quiz_exception 1320 */ 1321 protected static function validate_attempt_review($params) { 1322 1323 $attemptobj = quiz_attempt::create($params['attemptid']); 1324 $attemptobj->check_review_capability(); 1325 1326 $displayoptions = $attemptobj->get_display_options(true); 1327 if ($attemptobj->is_own_attempt()) { 1328 if (!$attemptobj->is_finished()) { 1329 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'attemptclosed'); 1330 } else if (!$displayoptions->attempt) { 1331 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreview', null, '', 1332 $attemptobj->cannot_review_message()); 1333 } 1334 } else if (!$attemptobj->is_review_allowed()) { 1335 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'noreviewattempt'); 1336 } 1337 return array($attemptobj, $displayoptions); 1338 } 1339 1340 /** 1341 * Describes the parameters for get_attempt_review. 1342 * 1343 * @return external_function_parameters 1344 * @since Moodle 3.1 1345 */ 1346 public static function get_attempt_review_parameters() { 1347 return new external_function_parameters ( 1348 array( 1349 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1350 'page' => new external_value(PARAM_INT, 'page number, empty for all the questions in all the pages', 1351 VALUE_DEFAULT, -1), 1352 ) 1353 ); 1354 } 1355 1356 /** 1357 * Returns review information for the given finished attempt, can be used by users or teachers. 1358 * 1359 * @param int $attemptid attempt id 1360 * @param int $page page number, empty for all the questions in all the pages 1361 * @return array of warnings and the attempt data, feedback and questions 1362 * @since Moodle 3.1 1363 * @throws moodle_exception 1364 * @throws moodle_quiz_exception 1365 */ 1366 public static function get_attempt_review($attemptid, $page = -1) { 1367 global $PAGE; 1368 1369 $warnings = array(); 1370 1371 $params = array( 1372 'attemptid' => $attemptid, 1373 'page' => $page, 1374 ); 1375 $params = self::validate_parameters(self::get_attempt_review_parameters(), $params); 1376 1377 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1378 1379 if ($params['page'] !== -1) { 1380 $page = $attemptobj->force_page_number_into_range($params['page']); 1381 } else { 1382 $page = 'all'; 1383 } 1384 1385 // Prepare the output. 1386 $result = array(); 1387 $result['attempt'] = $attemptobj->get_attempt(); 1388 $result['questions'] = self::get_attempt_questions_data($attemptobj, true, $page, true); 1389 1390 $result['additionaldata'] = array(); 1391 // Summary data (from behaviours). 1392 $summarydata = $attemptobj->get_additional_summary_data($displayoptions); 1393 foreach ($summarydata as $key => $data) { 1394 // This text does not need formatting (no need for external_format_[string|text]). 1395 $result['additionaldata'][] = array( 1396 'id' => $key, 1397 'title' => $data['title'], $attemptobj->get_quizobj()->get_context()->id, 1398 'content' => $data['content'], 1399 ); 1400 } 1401 1402 // Feedback if there is any, and the user is allowed to see it now. 1403 $grade = quiz_rescale_grade($attemptobj->get_attempt()->sumgrades, $attemptobj->get_quiz(), false); 1404 1405 $feedback = $attemptobj->get_overall_feedback($grade); 1406 if ($displayoptions->overallfeedback && $feedback) { 1407 $result['additionaldata'][] = array( 1408 'id' => 'feedback', 1409 'title' => get_string('feedback', 'quiz'), 1410 'content' => $feedback, 1411 ); 1412 } 1413 1414 $result['grade'] = $grade; 1415 $result['warnings'] = $warnings; 1416 return $result; 1417 } 1418 1419 /** 1420 * Describes the get_attempt_review return value. 1421 * 1422 * @return external_single_structure 1423 * @since Moodle 3.1 1424 */ 1425 public static function get_attempt_review_returns() { 1426 return new external_single_structure( 1427 array( 1428 'grade' => new external_value(PARAM_RAW, 'grade for the quiz (or empty or "notyetgraded")'), 1429 'attempt' => self::attempt_structure(), 1430 'additionaldata' => new external_multiple_structure( 1431 new external_single_structure( 1432 array( 1433 'id' => new external_value(PARAM_ALPHANUMEXT, 'id of the data'), 1434 'title' => new external_value(PARAM_TEXT, 'data title'), 1435 'content' => new external_value(PARAM_RAW, 'data content'), 1436 ) 1437 ) 1438 ), 1439 'questions' => new external_multiple_structure(self::question_structure()), 1440 'warnings' => new external_warnings(), 1441 ) 1442 ); 1443 } 1444 1445 /** 1446 * Describes the parameters for view_attempt. 1447 * 1448 * @return external_function_parameters 1449 * @since Moodle 3.1 1450 */ 1451 public static function view_attempt_parameters() { 1452 return new external_function_parameters ( 1453 array( 1454 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1455 'page' => new external_value(PARAM_INT, 'page number'), 1456 'preflightdata' => new external_multiple_structure( 1457 new external_single_structure( 1458 array( 1459 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1460 'value' => new external_value(PARAM_RAW, 'data value'), 1461 ) 1462 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1463 ) 1464 ) 1465 ); 1466 } 1467 1468 /** 1469 * Trigger the attempt viewed event. 1470 * 1471 * @param int $attemptid attempt id 1472 * @param int $page page number 1473 * @param array $preflightdata preflight required data (like passwords) 1474 * @return array of warnings and status result 1475 * @since Moodle 3.1 1476 */ 1477 public static function view_attempt($attemptid, $page, $preflightdata = array()) { 1478 1479 $warnings = array(); 1480 1481 $params = array( 1482 'attemptid' => $attemptid, 1483 'page' => $page, 1484 'preflightdata' => $preflightdata, 1485 ); 1486 $params = self::validate_parameters(self::view_attempt_parameters(), $params); 1487 list($attemptobj, $messages) = self::validate_attempt($params); 1488 1489 // Log action. 1490 $attemptobj->fire_attempt_viewed_event(); 1491 1492 // Update attempt page, throwing an exception if $page is not valid. 1493 if (!$attemptobj->set_currentpage($params['page'])) { 1494 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'Out of sequence access'); 1495 } 1496 1497 $result = array(); 1498 $result['status'] = true; 1499 $result['warnings'] = $warnings; 1500 return $result; 1501 } 1502 1503 /** 1504 * Describes the view_attempt return value. 1505 * 1506 * @return external_single_structure 1507 * @since Moodle 3.1 1508 */ 1509 public static function view_attempt_returns() { 1510 return new external_single_structure( 1511 array( 1512 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1513 'warnings' => new external_warnings(), 1514 ) 1515 ); 1516 } 1517 1518 /** 1519 * Describes the parameters for view_attempt_summary. 1520 * 1521 * @return external_function_parameters 1522 * @since Moodle 3.1 1523 */ 1524 public static function view_attempt_summary_parameters() { 1525 return new external_function_parameters ( 1526 array( 1527 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1528 'preflightdata' => new external_multiple_structure( 1529 new external_single_structure( 1530 array( 1531 'name' => new external_value(PARAM_ALPHANUMEXT, 'data name'), 1532 'value' => new external_value(PARAM_RAW, 'data value'), 1533 ) 1534 ), 'Preflight required data (like passwords)', VALUE_DEFAULT, array() 1535 ) 1536 ) 1537 ); 1538 } 1539 1540 /** 1541 * Trigger the attempt summary viewed event. 1542 * 1543 * @param int $attemptid attempt id 1544 * @param array $preflightdata preflight required data (like passwords) 1545 * @return array of warnings and status result 1546 * @since Moodle 3.1 1547 */ 1548 public static function view_attempt_summary($attemptid, $preflightdata = array()) { 1549 1550 $warnings = array(); 1551 1552 $params = array( 1553 'attemptid' => $attemptid, 1554 'preflightdata' => $preflightdata, 1555 ); 1556 $params = self::validate_parameters(self::view_attempt_summary_parameters(), $params); 1557 list($attemptobj, $messages) = self::validate_attempt($params); 1558 1559 // Log action. 1560 $attemptobj->fire_attempt_summary_viewed_event(); 1561 1562 $result = array(); 1563 $result['status'] = true; 1564 $result['warnings'] = $warnings; 1565 return $result; 1566 } 1567 1568 /** 1569 * Describes the view_attempt_summary return value. 1570 * 1571 * @return external_single_structure 1572 * @since Moodle 3.1 1573 */ 1574 public static function view_attempt_summary_returns() { 1575 return new external_single_structure( 1576 array( 1577 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1578 'warnings' => new external_warnings(), 1579 ) 1580 ); 1581 } 1582 1583 /** 1584 * Describes the parameters for view_attempt_review. 1585 * 1586 * @return external_function_parameters 1587 * @since Moodle 3.1 1588 */ 1589 public static function view_attempt_review_parameters() { 1590 return new external_function_parameters ( 1591 array( 1592 'attemptid' => new external_value(PARAM_INT, 'attempt id'), 1593 ) 1594 ); 1595 } 1596 1597 /** 1598 * Trigger the attempt reviewed event. 1599 * 1600 * @param int $attemptid attempt id 1601 * @return array of warnings and status result 1602 * @since Moodle 3.1 1603 */ 1604 public static function view_attempt_review($attemptid) { 1605 1606 $warnings = array(); 1607 1608 $params = array( 1609 'attemptid' => $attemptid, 1610 ); 1611 $params = self::validate_parameters(self::view_attempt_review_parameters(), $params); 1612 list($attemptobj, $displayoptions) = self::validate_attempt_review($params); 1613 1614 // Log action. 1615 $attemptobj->fire_attempt_reviewed_event(); 1616 1617 $result = array(); 1618 $result['status'] = true; 1619 $result['warnings'] = $warnings; 1620 return $result; 1621 } 1622 1623 /** 1624 * Describes the view_attempt_review return value. 1625 * 1626 * @return external_single_structure 1627 * @since Moodle 3.1 1628 */ 1629 public static function view_attempt_review_returns() { 1630 return new external_single_structure( 1631 array( 1632 'status' => new external_value(PARAM_BOOL, 'status: true if success'), 1633 'warnings' => new external_warnings(), 1634 ) 1635 ); 1636 } 1637 1638 /** 1639 * Describes the parameters for view_quiz. 1640 * 1641 * @return external_function_parameters 1642 * @since Moodle 3.1 1643 */ 1644 public static function get_quiz_feedback_for_grade_parameters() { 1645 return new external_function_parameters ( 1646 array( 1647 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1648 'grade' => new external_value(PARAM_FLOAT, 'the grade to check'), 1649 ) 1650 ); 1651 } 1652 1653 /** 1654 * Get the feedback text that should be show to a student who got the given grade in the given quiz. 1655 * 1656 * @param int $quizid quiz instance id 1657 * @param float $grade the grade to check 1658 * @return array of warnings and status result 1659 * @since Moodle 3.1 1660 * @throws moodle_exception 1661 */ 1662 public static function get_quiz_feedback_for_grade($quizid, $grade) { 1663 global $DB; 1664 1665 $params = array( 1666 'quizid' => $quizid, 1667 'grade' => $grade, 1668 ); 1669 $params = self::validate_parameters(self::get_quiz_feedback_for_grade_parameters(), $params); 1670 $warnings = array(); 1671 1672 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1673 1674 $result = array(); 1675 $result['feedbacktext'] = ''; 1676 $result['feedbacktextformat'] = FORMAT_MOODLE; 1677 1678 $feedback = quiz_feedback_record_for_grade($params['grade'], $quiz); 1679 if (!empty($feedback->feedbacktext)) { 1680 list($text, $format) = external_format_text($feedback->feedbacktext, $feedback->feedbacktextformat, $context->id, 1681 'mod_quiz', 'feedback', $feedback->id); 1682 $result['feedbacktext'] = $text; 1683 $result['feedbacktextformat'] = $format; 1684 $feedbackinlinefiles = external_util::get_area_files($context->id, 'mod_quiz', 'feedback', $feedback->id); 1685 if (!empty($feedbackinlinefiles)) { 1686 $result['feedbackinlinefiles'] = $feedbackinlinefiles; 1687 } 1688 } 1689 1690 $result['warnings'] = $warnings; 1691 return $result; 1692 } 1693 1694 /** 1695 * Describes the get_quiz_feedback_for_grade return value. 1696 * 1697 * @return external_single_structure 1698 * @since Moodle 3.1 1699 */ 1700 public static function get_quiz_feedback_for_grade_returns() { 1701 return new external_single_structure( 1702 array( 1703 'feedbacktext' => new external_value(PARAM_RAW, 'the comment that corresponds to this grade (empty for none)'), 1704 'feedbacktextformat' => new external_format_value('feedbacktext', VALUE_OPTIONAL), 1705 'feedbackinlinefiles' => new external_files('feedback inline files', VALUE_OPTIONAL), 1706 'warnings' => new external_warnings(), 1707 ) 1708 ); 1709 } 1710 1711 /** 1712 * Describes the parameters for get_quiz_access_information. 1713 * 1714 * @return external_function_parameters 1715 * @since Moodle 3.1 1716 */ 1717 public static function get_quiz_access_information_parameters() { 1718 return new external_function_parameters ( 1719 array( 1720 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1721 ) 1722 ); 1723 } 1724 1725 /** 1726 * Return access information for a given quiz. 1727 * 1728 * @param int $quizid quiz instance id 1729 * @return array of warnings and the access information 1730 * @since Moodle 3.1 1731 * @throws moodle_quiz_exception 1732 */ 1733 public static function get_quiz_access_information($quizid) { 1734 global $DB, $USER; 1735 1736 $warnings = array(); 1737 1738 $params = array( 1739 'quizid' => $quizid 1740 ); 1741 $params = self::validate_parameters(self::get_quiz_access_information_parameters(), $params); 1742 1743 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1744 1745 $result = array(); 1746 // Capabilities first. 1747 $result['canattempt'] = has_capability('mod/quiz:attempt', $context);; 1748 $result['canmanage'] = has_capability('mod/quiz:manage', $context);; 1749 $result['canpreview'] = has_capability('mod/quiz:preview', $context);; 1750 $result['canreviewmyattempts'] = has_capability('mod/quiz:reviewmyattempts', $context);; 1751 $result['canviewreports'] = has_capability('mod/quiz:viewreports', $context);; 1752 1753 // Access manager now. 1754 $quizobj = quiz::create($cm->instance, $USER->id); 1755 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1756 $timenow = time(); 1757 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1758 1759 $result['accessrules'] = $accessmanager->describe_rules(); 1760 $result['activerulenames'] = $accessmanager->get_active_rule_names(); 1761 $result['preventaccessreasons'] = $accessmanager->prevent_access(); 1762 1763 $result['warnings'] = $warnings; 1764 return $result; 1765 } 1766 1767 /** 1768 * Describes the get_quiz_access_information return value. 1769 * 1770 * @return external_single_structure 1771 * @since Moodle 3.1 1772 */ 1773 public static function get_quiz_access_information_returns() { 1774 return new external_single_structure( 1775 array( 1776 'canattempt' => new external_value(PARAM_BOOL, 'Whether the user can do the quiz or not.'), 1777 'canmanage' => new external_value(PARAM_BOOL, 'Whether the user can edit the quiz settings or not.'), 1778 'canpreview' => new external_value(PARAM_BOOL, 'Whether the user can preview the quiz or not.'), 1779 'canreviewmyattempts' => new external_value(PARAM_BOOL, 'Whether the users can review their previous attempts 1780 or not.'), 1781 'canviewreports' => new external_value(PARAM_BOOL, 'Whether the user can view the quiz reports or not.'), 1782 'accessrules' => new external_multiple_structure( 1783 new external_value(PARAM_TEXT, 'rule description'), 'list of rules'), 1784 'activerulenames' => new external_multiple_structure( 1785 new external_value(PARAM_PLUGIN, 'rule plugin names'), 'list of active rules'), 1786 'preventaccessreasons' => new external_multiple_structure( 1787 new external_value(PARAM_TEXT, 'access restriction description'), 'list of reasons'), 1788 'warnings' => new external_warnings(), 1789 ) 1790 ); 1791 } 1792 1793 /** 1794 * Describes the parameters for get_attempt_access_information. 1795 * 1796 * @return external_function_parameters 1797 * @since Moodle 3.1 1798 */ 1799 public static function get_attempt_access_information_parameters() { 1800 return new external_function_parameters ( 1801 array( 1802 'quizid' => new external_value(PARAM_INT, 'quiz instance id'), 1803 'attemptid' => new external_value(PARAM_INT, 'attempt id, 0 for the user last attempt if exists', VALUE_DEFAULT, 0), 1804 ) 1805 ); 1806 } 1807 1808 /** 1809 * Return access information for a given attempt in a quiz. 1810 * 1811 * @param int $quizid quiz instance id 1812 * @param int $attemptid attempt id, 0 for the user last attempt if exists 1813 * @return array of warnings and the access information 1814 * @since Moodle 3.1 1815 * @throws moodle_quiz_exception 1816 */ 1817 public static function get_attempt_access_information($quizid, $attemptid = 0) { 1818 global $DB, $USER; 1819 1820 $warnings = array(); 1821 1822 $params = array( 1823 'quizid' => $quizid, 1824 'attemptid' => $attemptid, 1825 ); 1826 $params = self::validate_parameters(self::get_attempt_access_information_parameters(), $params); 1827 1828 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1829 1830 $attempttocheck = 0; 1831 if (!empty($params['attemptid'])) { 1832 $attemptobj = quiz_attempt::create($params['attemptid']); 1833 if ($attemptobj->get_userid() != $USER->id) { 1834 throw new moodle_quiz_exception($attemptobj->get_quizobj(), 'notyourattempt'); 1835 } 1836 $attempttocheck = $attemptobj->get_attempt(); 1837 } 1838 1839 // Access manager now. 1840 $quizobj = quiz::create($cm->instance, $USER->id); 1841 $ignoretimelimits = has_capability('mod/quiz:ignoretimelimits', $context, null, false); 1842 $timenow = time(); 1843 $accessmanager = new quiz_access_manager($quizobj, $timenow, $ignoretimelimits); 1844 1845 $attempts = quiz_get_user_attempts($quiz->id, $USER->id, 'finished', true); 1846 $lastfinishedattempt = end($attempts); 1847 if ($unfinishedattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) { 1848 $attempts[] = $unfinishedattempt; 1849 1850 // Check if the attempt is now overdue. In that case the state will change. 1851 $quizobj->create_attempt_object($unfinishedattempt)->handle_if_time_expired(time(), false); 1852 1853 if ($unfinishedattempt->state != quiz_attempt::IN_PROGRESS and $unfinishedattempt->state != quiz_attempt::OVERDUE) { 1854 $lastfinishedattempt = $unfinishedattempt; 1855 } 1856 } 1857 $numattempts = count($attempts); 1858 1859 if (!$attempttocheck) { 1860 $attempttocheck = $unfinishedattempt ? $unfinishedattempt : $lastfinishedattempt; 1861 } 1862 1863 $result = array(); 1864 $result['isfinished'] = $accessmanager->is_finished($numattempts, $lastfinishedattempt); 1865 $result['preventnewattemptreasons'] = $accessmanager->prevent_new_attempt($numattempts, $lastfinishedattempt); 1866 1867 if ($attempttocheck) { 1868 $endtime = $accessmanager->get_end_time($attempttocheck); 1869 $result['endtime'] = ($endtime === false) ? 0 : $endtime; 1870 $attemptid = $unfinishedattempt ? $unfinishedattempt->id : null; 1871 $result['ispreflightcheckrequired'] = $accessmanager->is_preflight_check_required($attemptid); 1872 } 1873 1874 $result['warnings'] = $warnings; 1875 return $result; 1876 } 1877 1878 /** 1879 * Describes the get_attempt_access_information return value. 1880 * 1881 * @return external_single_structure 1882 * @since Moodle 3.1 1883 */ 1884 public static function get_attempt_access_information_returns() { 1885 return new external_single_structure( 1886 array( 1887 'endtime' => new external_value(PARAM_INT, 'When the attempt must be submitted (determined by rules).', 1888 VALUE_OPTIONAL), 1889 'isfinished' => new external_value(PARAM_BOOL, 'Whether there is no way the user will ever be allowed to attempt.'), 1890 'ispreflightcheckrequired' => new external_value(PARAM_BOOL, 'whether a check is required before the user 1891 starts/continues his attempt.', VALUE_OPTIONAL), 1892 'preventnewattemptreasons' => new external_multiple_structure( 1893 new external_value(PARAM_TEXT, 'access restriction description'), 1894 'list of reasons'), 1895 'warnings' => new external_warnings(), 1896 ) 1897 ); 1898 } 1899 1900 /** 1901 * Describes the parameters for get_quiz_required_qtypes. 1902 * 1903 * @return external_function_parameters 1904 * @since Moodle 3.1 1905 */ 1906 public static function get_quiz_required_qtypes_parameters() { 1907 return new external_function_parameters ( 1908 array( 1909 'quizid' => new external_value(PARAM_INT, 'quiz instance id') 1910 ) 1911 ); 1912 } 1913 1914 /** 1915 * Return the potential question types that would be required for a given quiz. 1916 * Please note that for random question types we return the potential question types in the category choosen. 1917 * 1918 * @param int $quizid quiz instance id 1919 * @return array of warnings and the access information 1920 * @since Moodle 3.1 1921 * @throws moodle_quiz_exception 1922 */ 1923 public static function get_quiz_required_qtypes($quizid) { 1924 global $DB, $USER; 1925 1926 $warnings = array(); 1927 1928 $params = array( 1929 'quizid' => $quizid 1930 ); 1931 $params = self::validate_parameters(self::get_quiz_required_qtypes_parameters(), $params); 1932 1933 list($quiz, $course, $cm, $context) = self::validate_quiz($params['quizid']); 1934 1935 $quizobj = quiz::create($cm->instance, $USER->id); 1936 $quizobj->preload_questions(); 1937 $quizobj->load_questions(); 1938 1939 // Question types used. 1940 $result = array(); 1941 $result['questiontypes'] = $quizobj->get_all_question_types_used(true); 1942 $result['warnings'] = $warnings; 1943 return $result; 1944 } 1945 1946 /** 1947 * Describes the get_quiz_required_qtypes return value. 1948 * 1949 * @return external_single_structure 1950 * @since Moodle 3.1 1951 */ 1952 public static function get_quiz_required_qtypes_returns() { 1953 return new external_single_structure( 1954 array( 1955 'questiontypes' => new external_multiple_structure( 1956 new external_value(PARAM_PLUGIN, 'question type'), 'list of question types used in the quiz'), 1957 'warnings' => new external_warnings(), 1958 ) 1959 ); 1960 } 1961 1962 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body