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