|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00030 defined('MOODLE_INTERNAL') || die(); 00031 00032 00041 class moodle_quiz_exception extends moodle_exception { 00042 public function __construct($quizobj, $errorcode, $a = null, $link = '', $debuginfo = null) { 00043 if (!$link) { 00044 $link = $quizobj->view_url(); 00045 } 00046 parent::__construct($errorcode, 'quiz', $link, $a, $debuginfo); 00047 } 00048 } 00049 00050 00063 class quiz { 00064 // Fields initialised in the constructor. 00065 protected $course; 00066 protected $cm; 00067 protected $quiz; 00068 protected $context; 00069 protected $questionids; 00070 00071 // Fields set later if that data is needed. 00072 protected $questions = null; 00073 protected $accessmanager = null; 00074 protected $ispreviewuser = null; 00075 00076 // Constructor ========================================================================= 00085 public function __construct($quiz, $cm, $course, $getcontext = true) { 00086 $this->quiz = $quiz; 00087 $this->cm = $cm; 00088 $this->quiz->cmid = $this->cm->id; 00089 $this->course = $course; 00090 if ($getcontext && !empty($cm->id)) { 00091 $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); 00092 } 00093 $questionids = quiz_questions_in_quiz($this->quiz->questions); 00094 if ($questionids) { 00095 $this->questionids = explode(',', quiz_questions_in_quiz($this->quiz->questions)); 00096 } else { 00097 $this->questionids = array(); // Which idiot made explode(',', '') = array('')? 00098 } 00099 } 00100 00108 public static function create($quizid, $userid) { 00109 global $DB; 00110 00111 $quiz = quiz_access_manager::load_quiz_and_settings($quizid); 00112 $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST); 00113 $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST); 00114 00115 // Update quiz with override information 00116 $quiz = quiz_update_effective_access($quiz, $userid); 00117 00118 return new quiz($quiz, $cm, $course); 00119 } 00120 00121 // Functions for loading more data ===================================================== 00122 00126 public function preload_questions() { 00127 if (empty($this->questionids)) { 00128 throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url()); 00129 } 00130 $this->questions = question_preload_questions($this->questionids, 00131 'qqi.grade AS maxmark, qqi.id AS instance', 00132 '{quiz_question_instances} qqi ON qqi.quiz = :quizid AND q.id = qqi.question', 00133 array('quizid' => $this->quiz->id)); 00134 } 00135 00142 public function load_questions($questionids = null) { 00143 if (is_null($questionids)) { 00144 $questionids = $this->questionids; 00145 } 00146 $questionstoprocess = array(); 00147 foreach ($questionids as $id) { 00148 if (array_key_exists($id, $this->questions)) { 00149 $questionstoprocess[$id] = $this->questions[$id]; 00150 } 00151 } 00152 get_question_options($questionstoprocess); 00153 } 00154 00155 // Simple getters ====================================================================== 00157 public function get_courseid() { 00158 return $this->course->id; 00159 } 00160 00162 public function get_course() { 00163 return $this->course; 00164 } 00165 00167 public function get_quizid() { 00168 return $this->quiz->id; 00169 } 00170 00172 public function get_quiz() { 00173 return $this->quiz; 00174 } 00175 00177 public function get_quiz_name() { 00178 return $this->quiz->name; 00179 } 00180 00182 public function get_num_attempts_allowed() { 00183 return $this->quiz->attempts; 00184 } 00185 00187 public function get_cmid() { 00188 return $this->cm->id; 00189 } 00190 00192 public function get_cm() { 00193 return $this->cm; 00194 } 00195 00197 public function get_context() { 00198 return $this->context; 00199 } 00200 00205 public function is_preview_user() { 00206 if (is_null($this->ispreviewuser)) { 00207 $this->ispreviewuser = has_capability('mod/quiz:preview', $this->context); 00208 } 00209 return $this->ispreviewuser; 00210 } 00211 00215 public function has_questions() { 00216 return !empty($this->questionids); 00217 } 00218 00223 public function get_question($id) { 00224 return $this->questions[$id]; 00225 } 00226 00230 public function get_questions($questionids = null) { 00231 if (is_null($questionids)) { 00232 $questionids = $this->questionids; 00233 } 00234 $questions = array(); 00235 foreach ($questionids as $id) { 00236 if (!array_key_exists($id, $this->questions)) { 00237 throw new moodle_exception('cannotstartmissingquestion', 'quiz', $this->view_url()); 00238 } 00239 $questions[$id] = $this->questions[$id]; 00240 $this->ensure_question_loaded($id); 00241 } 00242 return $questions; 00243 } 00244 00250 public function get_access_manager($timenow) { 00251 if (is_null($this->accessmanager)) { 00252 $this->accessmanager = new quiz_access_manager($this, $timenow, 00253 has_capability('mod/quiz:ignoretimelimits', $this->context, null, false)); 00254 } 00255 return $this->accessmanager; 00256 } 00257 00261 public function has_capability($capability, $userid = null, $doanything = true) { 00262 return has_capability($capability, $this->context, $userid, $doanything); 00263 } 00264 00268 public function require_capability($capability, $userid = null, $doanything = true) { 00269 return require_capability($capability, $this->context, $userid, $doanything); 00270 } 00271 00272 // URLs related to this attempt ======================================================== 00276 public function view_url() { 00277 global $CFG; 00278 return $CFG->wwwroot . '/mod/quiz/view.php?id=' . $this->cm->id; 00279 } 00280 00284 public function edit_url() { 00285 global $CFG; 00286 return $CFG->wwwroot . '/mod/quiz/edit.php?cmid=' . $this->cm->id; 00287 } 00288 00294 public function attempt_url($attemptid, $page = 0) { 00295 global $CFG; 00296 $url = $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $attemptid; 00297 if ($page) { 00298 $url .= '&page=' . $page; 00299 } 00300 return $url; 00301 } 00302 00306 public function start_attempt_url($page = 0) { 00307 $params = array('cmid' => $this->cm->id, 'sesskey' => sesskey()); 00308 if ($page) { 00309 $params['page'] = $page; 00310 } 00311 return new moodle_url('/mod/quiz/startattempt.php', $params); 00312 } 00313 00318 public function review_url($attemptid) { 00319 return new moodle_url('/mod/quiz/review.php', array('attempt' => $attemptid)); 00320 } 00321 00322 // Bits of content ===================================================================== 00323 00329 public function confirm_start_attempt_message($unfinished) { 00330 if ($unfinished) { 00331 return ''; 00332 } 00333 00334 if ($this->quiz->timelimit && $this->quiz->attempts) { 00335 return get_string('confirmstartattempttimelimit', 'quiz', $this->quiz->attempts); 00336 } else if ($this->quiz->timelimit) { 00337 return get_string('confirmstarttimelimit', 'quiz'); 00338 } else if ($this->quiz->attempts) { 00339 return get_string('confirmstartattemptlimit', 'quiz', $this->quiz->attempts); 00340 } 00341 00342 return ''; 00343 } 00344 00354 public function cannot_review_message($when, $short = false) { 00355 00356 if ($short) { 00357 $langstrsuffix = 'short'; 00358 $dateformat = get_string('strftimedatetimeshort', 'langconfig'); 00359 } else { 00360 $langstrsuffix = ''; 00361 $dateformat = ''; 00362 } 00363 00364 if ($when == mod_quiz_display_options::DURING || 00365 $when == mod_quiz_display_options::IMMEDIATELY_AFTER) { 00366 return ''; 00367 } else if ($when == mod_quiz_display_options::LATER_WHILE_OPEN && $this->quiz->timeclose && 00368 $this->quiz->reviewattempt & mod_quiz_display_options::AFTER_CLOSE) { 00369 return get_string('noreviewuntil' . $langstrsuffix, 'quiz', 00370 userdate($this->quiz->timeclose, $dateformat)); 00371 } else { 00372 return get_string('noreview' . $langstrsuffix, 'quiz'); 00373 } 00374 } 00375 00381 public function navigation($title) { 00382 global $PAGE; 00383 $PAGE->navbar->add($title); 00384 return ''; 00385 } 00386 00387 // Private methods ===================================================================== 00392 protected function ensure_question_loaded($id) { 00393 if (isset($this->questions[$id]->_partiallyloaded)) { 00394 throw new moodle_quiz_exception($this, 'questionnotloaded', $id); 00395 } 00396 } 00397 } 00398 00399 00408 class quiz_attempt { 00409 // Fields initialised in the constructor. 00410 protected $quizobj; 00411 protected $attempt; 00412 protected $quba; 00413 00414 // Fields set later if that data is needed. 00415 protected $pagelayout; // array page no => array of numbers on the page in order. 00416 protected $reviewoptions = null; 00417 00418 // Constructor ========================================================================= 00429 public function __construct($attempt, $quiz, $cm, $course, $loadquestions = true) { 00430 $this->attempt = $attempt; 00431 $this->quizobj = new quiz($quiz, $cm, $course); 00432 00433 if (!$loadquestions) { 00434 return; 00435 } 00436 00437 $this->quba = question_engine::load_questions_usage_by_activity($this->attempt->uniqueid); 00438 $this->determine_layout(); 00439 $this->number_questions(); 00440 } 00441 00446 protected static function create_helper($conditions) { 00447 global $DB; 00448 00449 $attempt = $DB->get_record('quiz_attempts', $conditions, '*', MUST_EXIST); 00450 $quiz = quiz_access_manager::load_quiz_and_settings($attempt->quiz); 00451 $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST); 00452 $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST); 00453 00454 // Update quiz with override information 00455 $quiz = quiz_update_effective_access($quiz, $attempt->userid); 00456 00457 return new quiz_attempt($attempt, $quiz, $cm, $course); 00458 } 00459 00466 public static function create($attemptid) { 00467 return self::create_helper(array('id' => $attemptid)); 00468 } 00469 00476 public static function create_from_usage_id($usageid) { 00477 return self::create_helper(array('uniqueid' => $usageid)); 00478 } 00479 00480 private function determine_layout() { 00481 $this->pagelayout = array(); 00482 00483 // Break up the layout string into pages. 00484 $pagelayouts = explode(',0', quiz_clean_layout($this->attempt->layout, true)); 00485 00486 // Strip off any empty last page (normally there is one). 00487 if (end($pagelayouts) == '') { 00488 array_pop($pagelayouts); 00489 } 00490 00491 // File the ids into the arrays. 00492 $this->pagelayout = array(); 00493 foreach ($pagelayouts as $page => $pagelayout) { 00494 $pagelayout = trim($pagelayout, ','); 00495 if ($pagelayout == '') { 00496 continue; 00497 } 00498 $this->pagelayout[$page] = explode(',', $pagelayout); 00499 } 00500 } 00501 00502 // Number the questions. 00503 private function number_questions() { 00504 $number = 1; 00505 foreach ($this->pagelayout as $page => $slots) { 00506 foreach ($slots as $slot) { 00507 $question = $this->quba->get_question($slot); 00508 if ($question->length > 0) { 00509 $question->_number = $number; 00510 $number += $question->length; 00511 } else { 00512 $question->_number = get_string('infoshort', 'quiz'); 00513 } 00514 $question->_page = $page; 00515 } 00516 } 00517 } 00518 00519 // Simple getters ====================================================================== 00520 public function get_quiz() { 00521 return $this->quizobj->get_quiz(); 00522 } 00523 00524 public function get_quizobj() { 00525 return $this->quizobj; 00526 } 00527 00529 public function get_courseid() { 00530 return $this->quizobj->get_courseid(); 00531 } 00532 00534 public function get_course() { 00535 return $this->quizobj->get_course(); 00536 } 00537 00539 public function get_quizid() { 00540 return $this->quizobj->get_quizid(); 00541 } 00542 00544 public function get_quiz_name() { 00545 return $this->quizobj->get_quiz_name(); 00546 } 00547 00549 public function get_cm() { 00550 return $this->quizobj->get_cm(); 00551 } 00552 00554 public function get_cmid() { 00555 return $this->quizobj->get_cmid(); 00556 } 00557 00562 public function is_preview_user() { 00563 return $this->quizobj->is_preview_user(); 00564 } 00565 00567 public function get_num_attempts_allowed() { 00568 return $this->quizobj->get_num_attempts_allowed(); 00569 } 00570 00572 public function get_num_pages() { 00573 return count($this->pagelayout); 00574 } 00575 00581 public function get_access_manager($timenow) { 00582 return $this->quizobj->get_access_manager($timenow); 00583 } 00584 00586 public function get_attemptid() { 00587 return $this->attempt->id; 00588 } 00589 00591 public function get_uniqueid() { 00592 return $this->attempt->uniqueid; 00593 } 00594 00596 public function get_attempt() { 00597 return $this->attempt; 00598 } 00599 00601 public function get_attempt_number() { 00602 return $this->attempt->attempt; 00603 } 00604 00606 public function get_userid() { 00607 return $this->attempt->userid; 00608 } 00609 00614 public function is_finished() { 00615 return $this->attempt->timefinish != 0; 00616 } 00617 00619 public function is_preview() { 00620 return $this->attempt->preview; 00621 } 00622 00630 public function is_own_attempt() { 00631 global $USER; 00632 return $this->attempt->userid == $USER->id && 00633 (!$this->is_preview_user() || $this->attempt->preview); 00634 } 00635 00639 public function is_own_preview() { 00640 global $USER; 00641 return $this->attempt->userid == $USER->id && 00642 $this->is_preview_user() && $this->attempt->preview; 00643 } 00644 00650 public function is_review_allowed() { 00651 if (!$this->has_capability('mod/quiz:viewreports')) { 00652 return false; 00653 } 00654 00655 $cm = $this->get_cm(); 00656 if ($this->has_capability('moodle/site:accessallgroups') || 00657 groups_get_activity_groupmode($cm) != SEPARATEGROUPS) { 00658 return true; 00659 } 00660 00661 // Check the users have at least one group in common. 00662 $teachersgroups = groups_get_activity_allowed_groups($cm); 00663 $studentsgroups = groups_get_all_groups( 00664 $cm->course, $this->attempt->userid, $cm->groupingid); 00665 return $teachersgroups && $studentsgroups && 00666 array_intersect(array_keys($teachersgroups), array_keys($studentsgroups)); 00667 } 00668 00673 public function get_overall_feedback($grade) { 00674 return quiz_feedback_for_grade($grade, $this->get_quiz(), 00675 $this->quizobj->get_context()); 00676 } 00677 00681 public function has_capability($capability, $userid = null, $doanything = true) { 00682 return $this->quizobj->has_capability($capability, $userid, $doanything); 00683 } 00684 00688 public function require_capability($capability, $userid = null, $doanything = true) { 00689 return $this->quizobj->require_capability($capability, $userid, $doanything); 00690 } 00691 00696 public function check_review_capability() { 00697 if (!$this->has_capability('mod/quiz:viewreports')) { 00698 if ($this->get_attempt_state() == mod_quiz_display_options::IMMEDIATELY_AFTER) { 00699 $this->require_capability('mod/quiz:attempt'); 00700 } else { 00701 $this->require_capability('mod/quiz:reviewmyattempts'); 00702 } 00703 } 00704 } 00705 00710 public function get_attempt_state() { 00711 return quiz_attempt_state($this->get_quiz(), $this->attempt); 00712 } 00713 00720 public function get_display_options($reviewing) { 00721 if ($reviewing) { 00722 if (is_null($this->reviewoptions)) { 00723 $this->reviewoptions = quiz_get_review_options($this->get_quiz(), 00724 $this->attempt, $this->quizobj->get_context()); 00725 } 00726 return $this->reviewoptions; 00727 00728 } else { 00729 $options = mod_quiz_display_options::make_from_quiz($this->get_quiz(), 00730 mod_quiz_display_options::DURING); 00731 $options->flags = quiz_get_flag_option($this->attempt, $this->quizobj->get_context()); 00732 return $options; 00733 } 00734 } 00735 00748 public function get_display_options_with_edit_link($reviewing, $slot, $thispageurl) { 00749 $options = clone($this->get_display_options($reviewing)); 00750 00751 if (!$thispageurl) { 00752 return $options; 00753 } 00754 00755 if (!($reviewing || $this->is_preview())) { 00756 return $options; 00757 } 00758 00759 $question = $this->quba->get_question($slot); 00760 if (!question_has_capability_on($question, 'edit', $question->category)) { 00761 return $options; 00762 } 00763 00764 $options->editquestionparams['cmid'] = $this->get_cmid(); 00765 $options->editquestionparams['returnurl'] = $thispageurl; 00766 00767 return $options; 00768 } 00769 00774 public function is_last_page($page) { 00775 return $page == count($this->pagelayout) - 1; 00776 } 00777 00785 public function get_slots($page = 'all') { 00786 if ($page === 'all') { 00787 $numbers = array(); 00788 foreach ($this->pagelayout as $numbersonpage) { 00789 $numbers = array_merge($numbers, $numbersonpage); 00790 } 00791 return $numbers; 00792 } else { 00793 return $this->pagelayout[$page]; 00794 } 00795 } 00796 00802 public function get_question_attempt($slot) { 00803 return $this->quba->get_question_attempt($slot); 00804 } 00805 00811 public function is_real_question($slot) { 00812 return $this->quba->get_question($slot)->length != 0; 00813 } 00814 00820 public function is_question_flagged($slot) { 00821 return $this->quba->get_question_attempt($slot)->is_flagged(); 00822 } 00823 00833 public function get_question_number($slot) { 00834 return $this->quba->get_question($slot)->_number; 00835 } 00836 00846 public function get_question_name($slot) { 00847 return $this->quba->get_question($slot)->name; 00848 } 00849 00861 public function get_question_status($slot, $showcorrectness) { 00862 return $this->quba->get_question_state_string($slot, $showcorrectness); 00863 } 00864 00875 public function get_question_state_class($slot, $showcorrectness) { 00876 return $this->quba->get_question_state_class($slot, $showcorrectness); 00877 } 00878 00887 public function get_question_mark($slot) { 00888 return quiz_format_question_grade($this->get_quiz(), $this->quba->get_question_mark($slot)); 00889 } 00890 00896 public function get_question_action_time($slot) { 00897 return $this->quba->get_question_action_time($slot); 00898 } 00899 00900 // URLs related to this attempt ======================================================== 00904 public function view_url() { 00905 return $this->quizobj->view_url(); 00906 } 00907 00911 public function start_attempt_url($slot = null, $page = -1) { 00912 if ($page == -1 && !is_null($slot)) { 00913 $page = $this->quba->get_question($slot)->_page; 00914 } else { 00915 $page = 0; 00916 } 00917 return $this->quizobj->start_attempt_url($page); 00918 } 00919 00930 public function attempt_url($slot = null, $page = -1, $thispage = -1) { 00931 return $this->page_and_question_url('attempt', $slot, $page, false, $thispage); 00932 } 00933 00937 public function summary_url() { 00938 return new moodle_url('/mod/quiz/summary.php', array('attempt' => $this->attempt->id)); 00939 } 00940 00944 public function processattempt_url() { 00945 return new moodle_url('/mod/quiz/processattempt.php'); 00946 } 00947 00958 public function review_url($slot = null, $page = -1, $showall = false, $thispage = -1) { 00959 return $this->page_and_question_url('review', $slot, $page, $showall, $thispage); 00960 } 00961 00962 // Bits of content ===================================================================== 00963 00971 public function cannot_review_message($short = false) { 00972 return $this->quizobj->cannot_review_message( 00973 $this->get_attempt_state(), $short); 00974 } 00975 00980 public function get_html_head_contributions($page = 'all', $showall = false) { 00981 if ($showall) { 00982 $page = 'all'; 00983 } 00984 $result = ''; 00985 foreach ($this->get_slots($page) as $slot) { 00986 $result .= $this->quba->render_question_head_html($slot); 00987 } 00988 $result .= question_engine::initialise_js(); 00989 return $result; 00990 } 00991 00996 public function get_question_html_head_contributions($slot) { 00997 return $this->quba->render_question_head_html($slot) . 00998 question_engine::initialise_js(); 00999 } 01000 01005 public function restart_preview_button() { 01006 global $OUTPUT; 01007 if ($this->is_preview() && $this->is_preview_user()) { 01008 return $OUTPUT->single_button(new moodle_url( 01009 $this->start_attempt_url(), array('forcenew' => true)), 01010 get_string('startnewpreview', 'quiz')); 01011 } else { 01012 return ''; 01013 } 01014 } 01015 01025 public function render_question($slot, $reviewing, $thispageurl = null) { 01026 return $this->quba->render_question($slot, 01027 $this->get_display_options_with_edit_link($reviewing, $slot, $thispageurl), 01028 $this->quba->get_question($slot)->_number); 01029 } 01030 01041 public function render_question_at_step($slot, $seq, $reviewing, $thispageurl = '') { 01042 return $this->quba->render_question_at_step($slot, $seq, 01043 $this->get_display_options($reviewing), 01044 $this->quba->get_question($slot)->_number); 01045 } 01046 01052 public function render_question_for_commenting($slot) { 01053 $options = $this->get_display_options(true); 01054 $options->hide_all_feedback(); 01055 $options->manualcomment = question_display_options::EDITABLE; 01056 return $this->quba->render_question($slot, $options, 01057 $this->quba->get_question($slot)->_number); 01058 } 01059 01068 public function check_file_access($slot, $reviewing, $contextid, $component, 01069 $filearea, $args, $forcedownload) { 01070 return $this->quba->check_file_access($slot, $this->get_display_options($reviewing), 01071 $component, $filearea, $args, $forcedownload); 01072 } 01073 01082 public function get_navigation_panel(mod_quiz_renderer $output, 01083 $panelclass, $page, $showall = false) { 01084 $panel = new $panelclass($this, $this->get_display_options(true), $page, $showall); 01085 01086 $bc = new block_contents(); 01087 $bc->attributes['id'] = 'mod_quiz_navblock'; 01088 $bc->title = get_string('quiznavigation', 'quiz'); 01089 $bc->content = $output->navigation_panel($panel); 01090 return $bc; 01091 } 01092 01100 public function links_to_other_attempts(moodle_url $url) { 01101 $attempts = quiz_get_user_attempts($this->get_quiz()->id, $this->attempt->userid, 'all'); 01102 if (count($attempts) <= 1) { 01103 return false; 01104 } 01105 01106 $links = new mod_quiz_links_to_other_attempts(); 01107 foreach ($attempts as $at) { 01108 if ($at->id == $this->attempt->id) { 01109 $links->links[$at->attempt] = null; 01110 } else { 01111 $links->links[$at->attempt] = new moodle_url($url, array('attempt' => $at->id)); 01112 } 01113 } 01114 return $links; 01115 } 01116 01117 // Methods for processing ================================================== 01118 01125 public function process_all_actions($timestamp) { 01126 global $DB; 01127 $this->quba->process_all_actions($timestamp); 01128 question_engine::save_questions_usage_by_activity($this->quba); 01129 01130 $this->attempt->timemodified = $timestamp; 01131 if ($this->attempt->timefinish) { 01132 $this->attempt->sumgrades = $this->quba->get_total_mark(); 01133 } 01134 $DB->update_record('quiz_attempts', $this->attempt); 01135 01136 if (!$this->is_preview() && $this->attempt->timefinish) { 01137 quiz_save_best_grade($this->get_quiz(), $this->get_userid()); 01138 } 01139 } 01140 01145 public function save_question_flags() { 01146 $this->quba->update_question_flags(); 01147 question_engine::save_questions_usage_by_activity($this->quba); 01148 } 01149 01150 public function finish_attempt($timestamp) { 01151 global $DB, $USER; 01152 $this->quba->process_all_actions($timestamp); 01153 $this->quba->finish_all_questions($timestamp); 01154 01155 question_engine::save_questions_usage_by_activity($this->quba); 01156 01157 $this->attempt->timemodified = $timestamp; 01158 $this->attempt->timefinish = $timestamp; 01159 $this->attempt->sumgrades = $this->quba->get_total_mark(); 01160 $DB->update_record('quiz_attempts', $this->attempt); 01161 01162 if (!$this->is_preview()) { 01163 quiz_save_best_grade($this->get_quiz(), $this->attempt->userid); 01164 01165 // Trigger event 01166 $eventdata = new stdClass(); 01167 $eventdata->component = 'mod_quiz'; 01168 $eventdata->attemptid = $this->attempt->id; 01169 $eventdata->timefinish = $this->attempt->timefinish; 01170 $eventdata->userid = $this->attempt->userid; 01171 $eventdata->submitterid = $USER->id; 01172 $eventdata->quizid = $this->get_quizid(); 01173 $eventdata->cmid = $this->get_cmid(); 01174 $eventdata->courseid = $this->get_courseid(); 01175 events_trigger('quiz_attempt_submitted', $eventdata); 01176 01177 // Tell any access rules that care that the attempt is over. 01178 $this->get_access_manager($timestamp)->current_attempt_finished(); 01179 } 01180 } 01181 01187 public function question_print_comment_fields($slot, $prefix) { 01188 // Work out a nice title. 01189 $student = get_record('user', 'id', $this->get_userid()); 01190 $a = new object(); 01191 $a->fullname = fullname($student, true); 01192 $a->attempt = $this->get_attempt_number(); 01193 01194 question_print_comment_fields($this->quba->get_question_attempt($slot), 01195 $prefix, $this->get_display_options(true)->markdp, 01196 get_string('gradingattempt', 'quiz_grading', $a)); 01197 } 01198 01199 // Private methods ===================================================================== 01200 01215 protected function page_and_question_url($script, $slot, $page, $showall, $thispage) { 01216 // Fix up $page 01217 if ($page == -1) { 01218 if (!is_null($slot) && !$showall) { 01219 $page = $this->quba->get_question($slot)->_page; 01220 } else { 01221 $page = 0; 01222 } 01223 } 01224 01225 if ($showall) { 01226 $page = 0; 01227 } 01228 01229 // Add a fragment to scroll down to the question. 01230 $fragment = ''; 01231 if (!is_null($slot)) { 01232 if ($slot == reset($this->pagelayout[$page])) { 01233 // First question on page, go to top. 01234 $fragment = '#'; 01235 } else { 01236 $fragment = '#q' . $slot; 01237 } 01238 } 01239 01240 // Work out the correct start to the URL. 01241 if ($thispage == $page) { 01242 return new moodle_url($fragment); 01243 01244 } else { 01245 $url = new moodle_url('/mod/quiz/' . $script . '.php' . $fragment, 01246 array('attempt' => $this->attempt->id)); 01247 if ($showall) { 01248 $url->param('showall', 1); 01249 } else if ($page > 0) { 01250 $url->param('page', $page); 01251 } 01252 return $url; 01253 } 01254 } 01255 } 01256 01257 01265 class quiz_nav_question_button implements renderable { 01266 public $id; 01267 public $number; 01268 public $stateclass; 01269 public $statestring; 01270 public $currentpage; 01271 public $flagged; 01272 public $url; 01273 } 01274 01275 01284 abstract class quiz_nav_panel_base { 01286 protected $attemptobj; 01288 protected $options; 01290 protected $page; 01292 protected $showall; 01293 01294 public function __construct(quiz_attempt $attemptobj, 01295 question_display_options $options, $page, $showall) { 01296 $this->attemptobj = $attemptobj; 01297 $this->options = $options; 01298 $this->page = $page; 01299 $this->showall = $showall; 01300 } 01301 01302 public function get_question_buttons() { 01303 $buttons = array(); 01304 foreach ($this->attemptobj->get_slots() as $slot) { 01305 $qa = $this->attemptobj->get_question_attempt($slot); 01306 $showcorrectness = $this->options->correctness && $qa->has_marks(); 01307 01308 $button = new quiz_nav_question_button(); 01309 $button->id = 'quiznavbutton' . $slot; 01310 $button->number = $qa->get_question()->_number; 01311 $button->stateclass = $qa->get_state_class($showcorrectness); 01312 if (!$showcorrectness && $button->stateclass == 'notanswered') { 01313 $button->stateclass = 'complete'; 01314 } 01315 $button->statestring = $this->get_state_string($qa, $showcorrectness); 01316 $button->currentpage = $qa->get_question()->_page == $this->page; 01317 $button->flagged = $qa->is_flagged(); 01318 $button->url = $this->get_question_url($slot); 01319 $buttons[] = $button; 01320 } 01321 01322 return $buttons; 01323 } 01324 01325 protected function get_state_string(question_attempt $qa, $showcorrectness) { 01326 if ($qa->get_question()->length > 0) { 01327 return $qa->get_state_string($showcorrectness); 01328 } 01329 01330 // Special case handling for 'information' items. 01331 if ($qa->get_state() == question_state::$todo) { 01332 return get_string('notyetviewed', 'quiz'); 01333 } else { 01334 return get_string('viewed', 'quiz'); 01335 } 01336 } 01337 01338 public function render_before_button_bits(mod_quiz_renderer $output) { 01339 return ''; 01340 } 01341 01342 abstract public function render_end_bits(mod_quiz_renderer $output); 01343 01344 protected function render_restart_preview_link($output) { 01345 if (!$this->attemptobj->is_own_preview()) { 01346 return ''; 01347 } 01348 return $output->restart_preview_button(new moodle_url( 01349 $this->attemptobj->start_attempt_url(), array('forcenew' => true))); 01350 } 01351 01352 protected abstract function get_question_url($slot); 01353 01354 public function user_picture() { 01355 global $DB; 01356 01357 if (!$this->attemptobj->get_quiz()->showuserpicture) { 01358 return null; 01359 } 01360 01361 $user = $DB->get_record('user', array('id' => $this->attemptobj->get_userid())); 01362 $userpicture = new user_picture($user); 01363 $userpicture->courseid = $this->attemptobj->get_courseid(); 01364 return $userpicture; 01365 } 01366 } 01367 01368 01376 class quiz_attempt_nav_panel extends quiz_nav_panel_base { 01377 public function get_question_url($slot) { 01378 return $this->attemptobj->attempt_url($slot, -1, $this->page); 01379 } 01380 01381 public function render_before_button_bits(mod_quiz_renderer $output) { 01382 return html_writer::tag('div', get_string('navnojswarning', 'quiz'), 01383 array('id' => 'quiznojswarning')); 01384 } 01385 01386 public function render_end_bits(mod_quiz_renderer $output) { 01387 return html_writer::link($this->attemptobj->summary_url(), 01388 get_string('endtest', 'quiz'), array('class' => 'endtestlink')) . 01389 $output->countdown_timer() . 01390 $this->render_restart_preview_link($output); 01391 } 01392 } 01393 01394 01402 class quiz_review_nav_panel extends quiz_nav_panel_base { 01403 public function get_question_url($slot) { 01404 return $this->attemptobj->review_url($slot, -1, $this->showall, $this->page); 01405 } 01406 01407 public function render_end_bits(mod_quiz_renderer $output) { 01408 $html = ''; 01409 if ($this->attemptobj->get_num_pages() > 1) { 01410 if ($this->showall) { 01411 $html .= html_writer::link($this->attemptobj->review_url(null, 0, false), 01412 get_string('showeachpage', 'quiz')); 01413 } else { 01414 $html .= html_writer::link($this->attemptobj->review_url(null, 0, true), 01415 get_string('showall', 'quiz')); 01416 } 01417 } 01418 $html .= $output->finish_review_link($this->attemptobj->view_url()); 01419 $html .= $this->render_restart_preview_link($output); 01420 return $html; 01421 } 01422 }