Moodle  2.2.1
http://www.collinsharper.com
C:/xampp/htdocs/moodle/mod/quiz/attemptlib.php
Go to the documentation of this file.
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 }
 All Data Structures Namespaces Files Functions Variables Enumerations