]> git.mjollnir.org Git - moodle.git/commitdiff
quiz: MDL-19891 fix that quiz navigation block that was broken by the blocks changes.
authortjhunt <tjhunt>
Thu, 23 Jul 2009 09:20:33 +0000 (09:20 +0000)
committertjhunt <tjhunt>
Thu, 23 Jul 2009 09:20:33 +0000 (09:20 +0000)
Sadly it now requries JavaScript again, at least for best operation.

lang/en_utf8/quiz.php
mod/quiz/attempt.php
mod/quiz/attemptlib.php
mod/quiz/processattempt.php
mod/quiz/quiz.js
mod/quiz/review.php
mod/quiz/summary.php
theme/standard/styles_color.css
theme/standard/styles_layout.css

index 0f9127ceebbe95e97668a226e2c77897db194161..3bb53d50e31d43e8e93eeab0b83a6cd06bfff599 100644 (file)
@@ -434,6 +434,7 @@ $string['multichoice'] = 'Multiple Choice';
 $string['multipleanswers'] = 'Choose at least one answer.';
 $string['multiplier'] = 'Multiplier';
 $string['name'] = 'Name';
+$string['navnojswarning'] = 'Warning: these links will not save your answers. Use the next button at the bottom of the page.';
 $string['neverallononepage'] = 'Never, all questions on one page';
 $string['newattemptfail'] = 'Error: Could not start a new attempt at the quiz';
 $string['newpage'] = 'New page';
@@ -675,6 +676,7 @@ $string['showbreaks'] = 'Show page breaks';
 $string['showcategorycontents'] = 'Show category contents $a->arrow';
 $string['showcorrectanswer'] = 'In feedback, show correct answers?';
 $string['showdetailedmarks'] = 'Show mark details';
+$string['showeachpage'] = 'Show one page at a time';
 $string['showfeedback'] = 'After answering, show feedback?';
 $string['showhidden'] = 'Also show old questions';
 $string['showinsecurepopup'] = 'Use a \'secure\' popup window for attempts';
index e3aa0e15405f2c8a0a7fe8878ed002273e6d6172..e19792c0fcd4581ff02340452a0a4b905173300c 100644 (file)
@@ -86,7 +86,6 @@
     $PAGE->blocks->add_pretend_block($navbc, $firstregion);
 
     // Print the page header
-    $PAGE->requires->yui_lib('event');
     $title = get_string('attempt', 'quiz', $attemptobj->get_attempt_number());
     $headtags = $attemptobj->get_html_head_contributions($page);
     if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
     // A quiz page with a lot of questions can take a long time to load, and we
     // want the protection afforded by init_quiz_form immediately, so include the
     // JS now.
-    echo $PAGE->requires->yui_lib('event')->asap();
-    echo $PAGE->requires->js('mod/quiz/quiz.js')->asap();
-    echo $PAGE->requires->js_function_call('init_quiz_form')->asap();
+    echo $PAGE->requires->js_function_call('init_quiz_form')->now();
     echo '<div>';
 
 /// Print all the questions
     echo '<div class="submitbtns">';
     if ($attemptobj->is_last_page($page)) {
         $nextpage = -1;
-        $nextpageforie = 'gotosummary';
     } else {
         $nextpage = $page + 1;
-        $nextpageforie = 'gotopage' . $nextpage;
     }
-    echo '<input type="submit" name="' . $nextpageforie . '" value="' . get_string('next') . '" />';
+    echo '<input type="submit" value="' . get_string('next') . '" />';
     echo "</div>";
 
     // Some hidden fields to trach what is going on.
     echo '<input type="hidden" name="attempt" value="' . $attemptobj->get_attemptid() . '" />';
-    echo '<input type="hidden" name="nextpage" value="' . $nextpage . '" />';
+    echo '<input type="hidden" name="nextpage" id="nextpagehiddeninput" value="' . $nextpage . '" />';
     echo '<input type="hidden" name="timeup" id="timeup" value="0" />';
     echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />';
 
index ac81a76a834985803b6671360c232787e020d0de..fc099ac2112845d636c8c834e1ea5ec58f62d9ac 100644 (file)
@@ -1,19 +1,39 @@
 <?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
 /**
- * This class handles loading all the information about a quiz attempt into memory,
- * and making it available for attemtp.php, summary.php and review.php.
- * Initially, it only loads a minimal amout of information about each attempt - loading
- * extra information only when necessary or when asked. The class tracks which questions
- * are loaded.
- *//** */
+ * Back-end code for handling data about quizzes and the current user's attempt.
+ *
+ * There are classes for loading all the information about a quiz and attempts,
+ * and for displaying the navigation panel.
+ *
+ * @package quiz
+ * @copyright 2008 onwards Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 
-if (!defined('MOODLE_INTERNAL')) {
-    die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page.
-}
 
 /**
  * Class for quiz exceptions. Just saves a couple of arguments on the
  * constructor for a moodle_exception.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
  */
 class moodle_quiz_exception extends moodle_exception {
     function __construct($quizobj, $errorcode, $a = NULL, $link = '', $debuginfo = null) {
@@ -25,8 +45,16 @@ class moodle_quiz_exception extends moodle_exception {
 }
 
 /**
- * A base class for holding and accessing information about a quiz and its questions,
- * before details of a particular attempt are loaded.
+ * A class encapsulating a quiz and the questions it contains, and making the
+ * information available to scripts like view.php.
+ *
+ * Initially, it only loads a minimal amout of information about each question - loading
+ * extra information only when necessary or when asked. The class tracks which questions
+ * are loaded.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
  */
 class quiz {
     // Fields initialised in the constructor.
@@ -63,10 +91,19 @@ class quiz {
     }
 
     // Functions for loading more data =====================================================
+    /**
+     * Convenience method. Calls {@link load_questions()} with the list of
+     * question ids for a given page.
+     *
+     * @param integer $page a page number.
+     */
     public function load_questions_on_page($page) {
         $this->load_questions($this->pagequestionids[$page]);
     }
 
+    /**
+     * Load just basic information about all the questions in this quiz.
+     */
     public function preload_questions() {
         if (empty($this->questionids)) {
             throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
@@ -79,7 +116,7 @@ class quiz {
     }
 
    /**
-     * Load some or all of the questions for this quiz.
+     * Fully load some or all of the questions for this quiz. You must call {@link preload_questions()} first.
      *
      * @param array $questionids question ids of the questions to load. null for all.
      */
@@ -301,14 +338,20 @@ class quiz {
     }
 
     // Private methods =====================================================================
-    // Check that the definition of a particular question is loaded, and if not throw an exception.
+    /**
+     *  Check that the definition of a particular question is loaded, and if not throw an exception.
+     *  @param $id a questionid.
+     */
     protected function ensure_question_loaded($id) {
         if (isset($this->questions[$id]->_partiallyloaded)) {
             throw new moodle_quiz_exception($this, 'questionnotloaded', $id);
         }
     }
 
-    private function determine_layout() {
+    /**
+     * Populate {@link $questionids} and {@link $pagequestionids} from the layout.
+     */
+    protected function determine_layout() {
         $this->questionids = array();
         $this->pagequestionids = array();
 
@@ -340,7 +383,9 @@ class quiz {
         }
     }
 
-    // Number the questions.
+    /**
+     * Number the questions, adding a _number field to each one.
+     */
     private function number_questions() {
         $number = 1;
         foreach ($this->pagequestionids as $page => $questionids) {
@@ -368,6 +413,10 @@ class quiz {
 /**
  * This class extends the quiz class to hold data about the state of a particular attempt,
  * in addition to the data about the quiz.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
  */
 class quiz_attempt extends quiz {
     // Fields initialised in the constructor.
@@ -424,7 +473,12 @@ class quiz_attempt extends quiz {
         }
     }
 
-    
+    /**
+     * Load basic information about the state of each question.
+     *
+     * This is enough to, for example, show the state of each question in the
+     * navigation panel, but only takes one DB query.
+     */
     public function preload_question_states() {
         if (empty($this->questionids)) {
             throw new moodle_quiz_exception($this, 'noquestions', $this->edit_url());
@@ -435,6 +489,14 @@ class quiz_attempt extends quiz {
         }
     }
 
+    /**
+     * Load a particular state of a particular question. Used by the reviewquestion.php
+     * script to let the teacher walk through the entire sequence of a student's
+     * interaction with a question.
+     *
+     * @param $questionid the question id
+     * @param $stateid the id of the particular state to load.
+     */
     public function load_specific_question_state($questionid, $stateid) {
         global $DB;
         $state = question_load_specific_state($this->questions[$questionid],
@@ -508,6 +570,12 @@ class quiz_attempt extends quiz {
         }
     }
 
+    /**
+     * Get the current state of a question in the attempt.
+     *
+     * @param $questionid a questionid.
+     * @return object the state.
+     */
     public function get_question_state($questionid) {
         $this->ensure_state_loaded($questionid);
         return $this->states[$questionid];
@@ -612,16 +680,16 @@ class quiz_attempt extends quiz {
 
     // URLs related to this attempt ========================================================
     /**
-     * @param integer $page if specified, the URL of this particular page of the attempt, otherwise
-     * the URL will go to the first page.
      * @param integer $questionid a question id. If set, will add a fragment to the URL
      * to jump to a particuar question on the page.
+     * @param integer $page if specified, the URL of this particular page of the attempt, otherwise
+     * the URL will go to the first page. If -1, deduce $page from $questionid.
+     * @param integer $thispage if not -1, the current page. Will cause links to other things on
+     * this page to be output as only a fragment.
      * @return string the URL to continue this attempt.
      */
-    public function attempt_url($questionid = 0, $page = -1) {
-        global $CFG;
-        return $CFG->wwwroot . '/mod/quiz/attempt.php?attempt=' . $this->attempt->id .
-                $this->page_and_question_fragment($questionid, $page);
+    public function attempt_url($questionid = 0, $page = -1, $thispage = -1) {
+        return $this->page_and_question_url('attempt', $questionid, $page, false, $thispage);
     }
 
     /**
@@ -641,32 +709,46 @@ class quiz_attempt extends quiz {
     }
 
     /**
+     * @param integer $questionid a question id. If set, will add a fragment to the URL
+     * to jump to a particuar question on the page. If -1, deduce $page from $questionid.
      * @param integer $page if specified, the URL of this particular page of the attempt, otherwise
      * the URL will go to the first page.
-     * @param integer $questionid a question id. If set, will add a fragment to the URL
-     * to jump to a particuar question on the page.
      * @param boolean $showall if true, the URL will be to review the entire attempt on one page,
      * and $page will be ignored.
-     * @param $otherattemptid if given, link to another attempt, instead of the one we represent.
+     * @param integer $thispage if not -1, the current page. Will cause links to other things on
+     * this page to be output as only a fragment.
      * @return string the URL to review this attempt.
      */
-    public function review_url($questionid = 0, $page = -1, $showall = false) {
-        global $CFG;
-        return $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $this->attempt->id .
-                $this->page_and_question_fragment($questionid, $page, $showall);
+    public function review_url($questionid = 0, $page = -1, $showall = false, $thispage = -1) {
+        return $this->page_and_question_url('review', $questionid, $page, $showall, $thispage);
     }
 
     // Bits of content =====================================================================
+    /**
+     * Initialise the JS etc. required all the questions on a page..
+     * @param mixed $page a page number, or 'all'.
+     */
     public function get_html_head_contributions($page = 'all') {
-        return get_html_head_contributions($this->get_question_ids($page),
-                $this->questions, $this->states);
+        global $PAGE;
+        // The JS does important things like navigation and so must be initialised
+        // as seen as possible, particularly if the page is loading slowly.
+        $PAGE->requires->yui_lib('dom')->in_head();
+        $PAGE->requires->yui_lib('event')->in_head();
+        $PAGE->requires->js('mod/quiz/quiz.js')->in_head();
+        get_html_head_contributions($this->get_question_ids($page), $this->questions, $this->states);
     }
 
+    /**
+     * Initialise the JS etc. required by one question.
+     * @param integer $questionid the question id.
+     */
     public function get_question_html_head_contributions($questionid) {
-        return get_html_head_contributions(array($questionid),
-                $this->questions, $this->states);
+        get_html_head_contributions(array($questionid), $this->questions, $this->states);
     }
 
+    /**
+     * Print the HTML for the start new preview button.
+     */
     public function print_restart_preview_button() {
         global $CFG;
         echo '<div class="controls">';
@@ -675,6 +757,10 @@ class quiz_attempt extends quiz {
         echo '</div>';
     }
 
+    /**
+     * Return the HTML of the quiz timer.
+     * @return string HTML content.
+     */
     public function get_timer_html() {
         return '<div id="quiz-timer">' . get_string('timeleft', 'quiz') .
                 ' <span id="quiz-time-left"></span></div>';
@@ -702,17 +788,34 @@ class quiz_attempt extends quiz {
                 $this->quiz, $options);
     }
 
+    /**
+     * Triggers the sending of the notification emails at the end of this attempt.
+     */
     public function quiz_send_notification_emails() {
         quiz_send_notification_emails($this->course, $this->quiz, $this->attempt,
                 $this->context, $this->cm);
     }
 
-    public function get_navigation_panel($panelclass, $page) {
-        $panel = new $panelclass($this, $this->get_review_options(), $page);
+    /**
+     * Get the navigation panel object for this attempt.
+     *
+     * @param $panelclass The type of panel, quiz_attempt_nav_panel or quiz_review_nav_panel
+     * @param $page the current page number.
+     * @param $showall whether we are showing the whole quiz on one page. (Used by review.php)
+     * @return quiz_nav_panel_base the requested object.
+     */
+    public function get_navigation_panel($panelclass, $page, $showall = false) {
+        $panel = new $panelclass($this, $this->get_review_options(), $page, $showall);
         return $panel->get_contents();
     }
 
-    /// List of all this user's attempts for people who can see reports.
+    /**
+     * Given a URL containing attempt={this attempt id}, return an array of variant URLs 
+     * @param $url a URL.
+     * @return string HTML fragment. Comma-separated list of links to the other
+     * attempts with the attempt number as the link text. The curent attempt is
+     * included but is not a link.
+     */
     public function links_to_other_attempts($url) {
         $search = '/\battempt=' . $this->attempt->id . '\b/';
         $attempts = quiz_get_user_attempts($this->quiz->id, $this->attempt->userid, 'all');
@@ -732,10 +835,19 @@ class quiz_attempt extends quiz {
     }
 
     // Methods for processing manual comments ==============================================
-    // I am not sure it is a good idea to have update methods here - this class is only
-    // about getting data out of the question engine, and helping to display it, apart from
-    // this.
+    /**
+     * Process a manual comment for a question in this attempt.
+     * @param $questionid
+     * @param integer $questionid the question id
+     * @param string $comment the new comment from the teacher.
+     * @param mixed $grade the grade the teacher assigned, or '' to not change the grade.
+     * @return mixed true on success, a string error message if a problem is detected
+     *         (for example score out of range).
+     */
     public function process_comment($questionid, $comment, $grade) {
+        // I am not sure it is a good idea to have update methods here - this
+        // class is only about getting data out of the question engine, and
+        // helping to display it, apart from this.
         $this->ensure_question_loaded($questionid);
         $this->ensure_state_loaded($questionid);
         $state = $this->states[$questionid];
@@ -755,6 +867,11 @@ class quiz_attempt extends quiz {
         return $error;
     }
 
+    /**
+     * Print the fields of the comment form for questions in this attempt.
+     * @param $questionid a question id.
+     * @param $prefix Prefix to add to all field names.
+     */
     public function question_print_comment_fields($questionid, $prefix) {
         global $DB;
 
@@ -772,7 +889,10 @@ class quiz_attempt extends quiz {
     }
 
     // Private methods =====================================================================
-    // Check that the state of a particular question is loaded, and if not throw an exception.
+    /**
+     * Check that the state of a particular question is loaded, and if not throw an exception.
+     * @param integer $id a question id.
+     */
     private function ensure_state_loaded($id) {
         if (!array_key_exists($id, $this->states) || isset($this->states[$id]->_partiallyloaded)) {
             throw new moodle_quiz_exception($this, 'statenotloaded', $id);
@@ -788,16 +908,23 @@ class quiz_attempt extends quiz {
     }
 
     /**
-     * Enter description here...
+     * Get a URL for a particular question on a particular page of the quiz.
+     * Used by {@link attempt_url()} and {@link review_url()}.
      *
-     * @param unknown_type $questionid the id of a particular question on the page to jump to.
-     * @param integer $page -1 to look up the page number from the questionid, otherwise the page number to use.
-     * @param boolean $showall
-     * @return string bit to add to the end of a URL.
+     * @param string $script. Used in the URL like /mod/quiz/$script.php
+     * @param integer $questionid the id of a particular question on the page to jump to. 0 to just use the $page parameter.
+     * @param integer $page -1 to look up the page number from the questionid, otherwise the page number to go to.
+     * @param boolean $showall if true, return a URL with showall=1, and not page number
+     * @param integer $thispage the page we are currently on. Links to questoins on this
+     *      page will just be a fragment #q123. -1 to disable this.
+     * @return The requested URL.
      */
-    private function page_and_question_fragment($questionid, $page, $showall = false) {
+    protected function page_and_question_url($script, $questionid, $page, $showall, $thispage) {
+        global $CFG;
+
+        // Fix up $page
         if ($page == -1) {
-            if ($questionid) {
+            if ($questionid && !$showall) {
                 $page = $this->questions[$questionid]->_page;
             } else {
                 $page = 0;
@@ -806,28 +933,40 @@ class quiz_attempt extends quiz {
         if ($showall) {
             $page = 0;
         }
-        $fragment = '';
+
+        // Work out the correct start to the URL.
+        if ($thispage == $page) {
+            $url = '';
+        } else {
+            $url = $CFG->wwwroot . '/mod/quiz/' . $script . '.php?attempt=' . $this->attempt->id;
+            if ($showall) {
+                $url .= '&showall=1';
+            } else if ($page > 0) {
+                $url .= '&page=' . $page;
+            }
+        }
+
+        // Add a fragment to scroll down ot the question.
         if ($questionid) {
             if ($questionid == reset($this->pagequestionids[$page])) {
                 // First question on page, go to top.
-                $fragment = '#';
+                $url .= '#';
             } else {
-                $fragment = '#q' . $questionid;
+                $url .= '#q' . $questionid;
             }
         }
-        $param = '';
-        if ($showall) {
-            $param = '&showall=1';
-        } else if ($page > 0) {
-            $param = '&page=' . $page;
-        }
-        return $param . $fragment;
+
+        return $url;
     }
 }
 
 /**
  * A PHP Iterator for conviniently looping over the questions in a quiz. The keys are the question
  * numbers (with 'i' for descriptions) and the values are the question objects.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
  */
 class quiz_attempt_question_iterator implements Iterator {
     private $attemptobj; // Reference to the quiz_attempt object we provide access to.
@@ -882,38 +1021,52 @@ class quiz_attempt_question_iterator implements Iterator {
     }
 }
 
+/**
+ * Represents the navigation panel, and builds a {@link block_contents} to allow
+ * it to be output.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
+ */
 abstract class quiz_nav_panel_base {
     protected $attemptobj;
     protected $options;
     protected $page;
+    protected $showall;
 
-    protected function __construct(quiz_attempt $attemptobj, $options, $page) {
+    public function __construct(quiz_attempt $attemptobj, $options, $page, $showall) {
           $this->attemptobj = $attemptobj;
           $this->options = $options;
           $this->page = $page;
+          $this->showall = $showall;
     }
 
     protected function get_question_buttons() {
-        global $PAGE;
         $html = '<div class="qn_buttons">' . "\n";
         foreach ($this->attemptobj->get_question_iterator() as $number => $question) {
             $html .= $this->get_question_button($number, $question) . "\n";
-            $PAGE->requires->js_function_call('quiz_init_nav_button',
-                    array($this->get_button_id($question), $question->id));
         }
         $html .= "</div>\n";
         return $html;
     }
 
-    protected function get_button_id($question) {
-        // The id to put on the button element in the HTML.
-        return 'quiznavbutton' . $question->id;
+    protected function get_question_button($number, $question) {
+        $strstate = get_string($this->attemptobj->get_question_status($question->id), 'quiz');
+        return '<a href="' . $this->get_question_url($question) .
+                '" class="qnbutton ' . $this->get_question_state_classes($question) .
+                '" id="quiznavbutton' . $question->id . '" title="' . $strstate . '">' .
+                $number . ' <span class="accesshide"> (' . $strstate . ')</span></a>';
     }
 
-    abstract protected function get_question_button($number, $question);
+    protected function get_before_button_bits() {
+        return '';
+    }
 
     abstract protected function get_end_bits();
 
+    abstract protected function get_question_url($question);
+
     protected function get_user_picture() {
         global $DB;
         $user = $DB->get_record('user', array('id' => $this->attemptobj->get_userid()));
@@ -930,7 +1083,7 @@ abstract class quiz_nav_panel_base {
         $classes = $this->attemptobj->get_question_status($question->id);
 
         // Plus a marker for the current page.
-        if ($question->_page == $this->page) {
+        if ($this->showall || $question->_page == $this->page) {
             $classes .= ' thispage';
         }
 
@@ -942,10 +1095,14 @@ abstract class quiz_nav_panel_base {
     }
 
     public function get_contents() {
+        global $PAGE;
+        $PAGE->requires->js_function_call('quiz_init_nav_flags');
+
         $content = '';
         if ($this->attemptobj->get_quiz()->showuserpicture) {
             $content .= $this->get_user_picture() . "\n";
         }
+        $content .= $this->get_before_button_bits();
         $content .= $this->get_question_buttons() . "\n";
         $content .= '<div class="othernav">' . "\n" . $this->get_end_bits() . "\n</div>\n";
 
@@ -957,49 +1114,54 @@ abstract class quiz_nav_panel_base {
     }
 }
 
+/**
+ * Specialisation of {@link quiz_nav_panel_base} for the attempt quiz page.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
+ */
 class quiz_attempt_nav_panel extends quiz_nav_panel_base {
-    public function __construct(quiz_attempt $attemptobj, $options, $page) {
-        parent::__construct($attemptobj, $options, $page);
+    protected function get_question_url($question) {
+        return $this->attemptobj->attempt_url($question->id, -1, $this->page);
     }
 
-    protected function get_question_button($number, $question) {
-        $questionsonpage = $this->attemptobj->get_question_ids($question->_page);
-        $onclick = '';
-        if ($question->id != reset($questionsonpage)) {
-            $onclick = ' onclick="form.action = form.action + \'#q' . $question->id .
-                '\'; return true;"';
-        }
-        return '<input type="submit" name="gotopage' . $question->_page .
-                '" value="' . $number . '" class="qnbutton ' .
-                $this->get_question_state_classes($question) . '" id="' .
-                $this->get_button_id($question) . '" ' . $onclick . '/>';
+    protected function get_before_button_bits() {
+        return '<div id="quiznojswarning">' . get_string('navnojswarning', 'quiz') . "</div>\n";
     }
 
     protected function get_end_bits() {
+        global $PAGE;
         $output = '';
-        $output .= '<input type="submit" name="gotosummary" value="' .
-                get_string('endtest', 'quiz') . '" class="endtestlink" />';
+        $output .= '<a href="' . $this->attemptobj->summary_url() . '" class="endtestlink">' . get_string('endtest', 'quiz') . '</a>';
         $output .= $this->attemptobj->get_timer_html();
+        $output .= $PAGE->requires->js_function_call('quiz_init_attempt_nav')->now();
         return $output;
     }
 }
 
+/**
+ * Specialisation of {@link quiz_nav_panel_base} for the review quiz page.
+ *
+ * @copyright 2008 Tim Hunt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
+ */
 class quiz_review_nav_panel extends quiz_nav_panel_base {
-    public function __construct(quiz_attempt $attemptobj, $options, $page) {
-        parent::__construct($attemptobj, $options, $page);
-    }
-
-    protected function get_question_button($number, $question) {
-        $strstate = get_string($this->attemptobj->get_question_status($question->id), 'quiz');
-        return '<a href="' . $this->attemptobj->review_url($question->id) .
-                '" class="qnbutton ' . $this->get_question_state_classes($question) . '" id="' .
-                $this->get_button_id($question) . '" title="' . $strstate . '">' . $number . '<span class="accesshide">(' . $strstate . '</span></a>';
+    protected function get_question_url($question) {
+        return $this->attemptobj->review_url($question->id, -1, $this->showall, $this->page);
     }
 
     protected function get_end_bits() {
+        $html = '';
+        if ($this->attemptobj->get_num_pages() > 1) {
+            if ($this->showall) {
+                $html = '<a href="' . $this->attemptobj->review_url(0, 0, false) . '">' . get_string('showeachpage', 'quiz') . '</a>';
+            } else {
+                $html = '<a href="' . $this->attemptobj->review_url(0, 0, true) . '">' . get_string('showall', 'quiz') . '</a>';
+            }
+        }
         $accessmanager = $this->attemptobj->get_access_manager(time());
-        $html = '<a href="' . $this->attemptobj->review_url(0, 0, true) . '">' .
-                get_string('showall', 'quiz') . '</a>';
         $html .= $accessmanager->print_finish_review_link($this->attemptobj->is_preview_user(), true);
         return $html;
     }
index afd5df40289e8f45b771252a591aa5b0e18bc5ad..cbabdc5094aed2a38061995233c5fc60d230dccd 100644 (file)
@@ -29,23 +29,6 @@ $timeup = optional_param('timeup', 0, PARAM_BOOL); // True if form was submitted
 
 $attemptobj = new quiz_attempt($attemptid);
 
-/// Because IE is buggy (see http://www.peterbe.com/plog/button-tag-in-IE) we cannot
-/// do the quiz navigation buttons as <button type="submit" name="page" value="N">Caption</button>.
-/// Instead we have to do them as <input type="submit" name="gotopageN" value="Caption"/> -
-/// at lest that seemed like the least horrible work-around to me. Therefore, we need to
-/// intercept gotopageN parameters here, and adjust $pgae accordingly.
-if (optional_param('gotosummary', false, PARAM_BOOL)) {
-    $nextpage = -1;
-} else {
-    $numpagesinquiz = $attemptobj->get_num_pages();
-    for ($i = 0; $i < $numpagesinquiz; $i++) {
-        if (optional_param('gotopage' . $i, false, PARAM_BOOL)) {
-            $nextpage = $i;
-            break;
-        }
-    }
-}
-
 /// Set $nexturl now. It will be updated if a particular question was sumbitted in
 /// adaptive mode.
 if ($nextpage == -1) {
@@ -163,8 +146,9 @@ if (!$finishattempt) {
 
 /// We have been asked to finish attempt, so do that //////////////////////
 
-/// Load the states of questions we have not done anything with, and reload the 
-/// ones we changed above.
+/// Now load the state of every question, reloading the ones we messed around
+/// with above.
+$attemptobj->preload_question_states();
 $attemptobj->load_question_states();
 
 /// Move each question to the closed state.
index 97cf4eeb68b9564375378bb62700865e448a553d..4d312baa11e120d09dbd34ebef801c1c2e473a6b 100644 (file)
@@ -148,11 +148,54 @@ quiz_timer = {
     }
 };
 
-// Initialise a button on the navigation panel.
-function quiz_init_nav_button(buttonid, questionid) {
-    // Arrange to be notified if the flagged state changes.
-    var button = document.getElementById(buttonid);
-    button.stateupdater = new quiz_nav_updater(button, questionid);
+// Set up synchronisation between question flags and the corresponding button in the nav panel.
+function quiz_init_nav_flags() {
+    var navblock = document.getElementById('quiznavigation');
+    var buttons = YAHOO.util.Dom.getElementsByClassName('qnbutton', 'a', navblock);
+    for (var i = 0; i < buttons.length; i++) {
+        var button = buttons[i];
+        var questionid = button.id.match(/\d+/)[0];
+        button.stateupdater = new quiz_nav_updater(button, questionid);
+    }
+}
+
+// Make the links in the attempt nav panel submit the form.
+function quiz_init_attempt_nav() {
+    var warning = document.getElementById('quiznojswarning');
+    warning.parentNode.removeChild(warning);
+    var navblock = document.getElementById('quiznavigation');
+    var buttons = YAHOO.util.Dom.getElementsByClassName('qnbutton', 'a', navblock);
+    for (var i = 0; i < buttons.length; i++) {
+        var button = buttons[i];
+        if (YAHOO.util.Dom.hasClass(button, 'thispage')) {
+            continue;
+        }
+        var pageidmatch = button.href.match(/page=(\d+)/);
+        var nav = {pageid: pageidmatch[1]};
+        var questionidmatch = button.href.match(/#q(\d+)/);
+        if (questionidmatch) {
+            nav.questionid = questionidmatch[1];
+        }
+        YAHOO.util.Event.addListener(button, 'click', quiz_nav_button_click, nav);
+    }
+    var endlink = YAHOO.util.Dom.getElementsByClassName('endtestlink', 'a', navblock)[0];
+    YAHOO.util.Event.addListener(endlink, 'click', quiz_end_test_click);
+}
+
+function quiz_nav_button_click(e, nav) {
+    YAHOO.util.Event.preventDefault(e);
+    document.getElementById('nextpagehiddeninput').value = nav.pageid;
+    var form = document.getElementById('responseform');
+    if (nav.questionid) {
+        form.action += '#q' + nav.questionid;
+    }
+    form.submit();
+}
+
+function quiz_end_test_click(e) {
+    YAHOO.util.Event.preventDefault(e);
+    document.getElementById('nextpagehiddeninput').value = -1;
+    document.getElementById('responseform').submit();
 }
 
 function quiz_nav_updater(element, questionid) {
index 9ffe1c4ea7fd2f9cd656464f6e2a77f41ce5d549..9a55256ca2725d99911c47a39df5b5aec3390cdc 100644 (file)
     }
 
 /// Arrange for the navigation to be displayed.
-    $navbc = $attemptobj->get_navigation_panel('quiz_review_nav_panel', $page);
+    $navbc = $attemptobj->get_navigation_panel('quiz_review_nav_panel', $page, $showall);
     $firstregion = reset($PAGE->blocks->get_regions());
     $PAGE->blocks->add_pretend_block($navbc, $firstregion);
 
 /// Print the page header
-    $PAGE->requires->js('mod/quiz/quiz.js');
     $headtags = $attemptobj->get_html_head_contributions($page);
     if ($accessmanager->securewindow_required($attemptobj->is_preview_user())) {
         $accessmanager->setup_secure_page($attemptobj->get_course()->shortname.': '.format_string($attemptobj->get_quiz_name()), $headtags);
     if ($lastpage) {
         $accessmanager->print_finish_review_link($attemptobj->is_preview_user());
     } else {
-        link_arrow_right(get_string('next'), s($attemptobj->review_url(0, $page + 1)));
+        echo link_arrow_right(get_string('next'), s($attemptobj->review_url(0, $page + 1)));
     }
     echo "</div>";
 
index d834aaf9916a9fd828cbe1b547fa95064ba96e7d..6ecc0a56ffafa2c05c7b387b94f29131b1306e8e 100644 (file)
@@ -68,7 +68,7 @@ if ($attemptobj->is_preview_user()) {
 print_heading($title);
 
 /// Prepare the summary table header
-$table->class = 'generaltable quizsummaryofattempt';
+$table->class = 'generaltable quizsummaryofattempt boxaligncenter';
 $table->head = array(get_string('question', 'quiz'), get_string('status', 'quiz'));
 $table->align = array('left', 'left');
 $table->size = array('', '');
index a12bb92f032352c108bac202946639db2d49bc4b..6acbb00c7466618b6f10737458220617b3c92cfb 100644 (file)
@@ -1174,7 +1174,9 @@ table.quizreviewsummary td.cell {
 #quiznavigation .qnbutton.incorrect {
   background-color: #fcc;
 }
-
+#quiznojswarning {
+  color: red;
+}
 #mod-quiz-report .dubious{
   background-color: #fcc;
 }
index a4b46e5e9a3d3de5e253380ed48d8746a385b2a4..2edc42babd1c5d2ad9875e0435d9b0e5da22ad26 100644 (file)
@@ -4377,8 +4377,12 @@ body.jsenabled .jshidewhenenabled {
   height: 16px;
   vertical-align: middle;
 }
-#mod-quiz-attempt #quiz-timer {
-  display: none;
+#mod-quiz-attempt #quiz-timer,
+#mod-quiz-summary #quiz-timer {
+  display: none; /* Revealed by JavaScript if applicable */
+}
+#mod-quiz-summary #quiz-timer {
+  margin-top: 1em;
 }
 #mod-quiz-attempt #quiz-time-left {
   font-weight: bold;
@@ -4612,13 +4616,20 @@ table.quizreviewsummary td.cell {
     float: left;
 }
 #quiznavigation .othernav {
-  clear: both;
+    clear: both;
 }
 #quiznavigation .othernav a,
 #quiznavigation .othernav input {
   display: block;
   margin: 0.5em 0;
 }
+#quiznojswarning {
+    font-size: 0.7em;
+    line-height: 1.1;
+}
+.jsenabled #quiznojswarning {
+    display: none;
+}
 .mod-quiz div.tabtree a span img.iconsmall {
   vertical-align: baseline;
 }