MDL-20636 Previewing a truefalse question in deferred feedback mode now works.
authorTim Hunt <T.J.Hunt@open.ac.uk>
Fri, 24 Dec 2010 14:51:56 +0000 (14:51 +0000)
committerTim Hunt <T.J.Hunt@open.ac.uk>
Thu, 13 Jan 2011 18:35:43 +0000 (18:35 +0000)
14 files changed:
lang/en/question.php
lib/questionlib.php
local/qedatabase/db/install.php
question/behaviour/behaviourbase.php
question/engine/bank.php
question/engine/datalib.php
question/engine/lib.php
question/preview.php
question/previewlib.php
question/todo/diffstat.txt
question/type/questionbase.php
question/type/questiontype.php
question/type/rendererbase.php
question/type/truefalse/renderer.php

index a7338d4..e902df5 100644 (file)
@@ -239,7 +239,7 @@ $string['answer'] = 'Answer';
 $string['answersaved'] = 'Answer saved';
 $string['attemptfinished'] = 'Attempt finished';
 $string['attemptfinishedsubmitting'] = 'Attempt finished submitting: ';
-$string['behaviourbeingused'] = 'behaviour being used: $a';
+$string['behaviourbeingused'] = 'behaviour being used: {$a}';
 $string['cannotloadquestion'] = 'Could not load question';
 $string['cannotpreview'] = 'You can\'t preview these questions!';
 $string['category'] = 'Category';
@@ -254,7 +254,7 @@ $string['commented'] = 'Commented: {$a}';
 $string['comment'] = 'Comment';
 $string['commentormark'] = 'Make comment or override mark';
 $string['comments'] = 'Comments';
-$string['commentx'] = 'Comment: $a';
+$string['commentx'] = 'Comment: {$a}';
 $string['complete'] = 'Complete';
 $string['contexterror'] = 'You shouldn\'t have got here if you\'re not moving a category to another context.';
 $string['correct'] = 'Correct';
@@ -273,6 +273,7 @@ $string['hidden'] = 'Hidden';
 $string['hintn'] = 'Hint {no}';
 $string['hinttext'] = 'Hint text';
 $string['howquestionsbehave'] = 'How questions behave';
+$string['howquestionsbehave_help'] = 'Students can interact with the questions in the quiz in various different ways. For example, you may wish the students to enter an answer to each question and then submit the entire quiz, before anything is graded or they get any feedback. That would be \'Deferred feedback\' mode. Alternatively, you may wish for students to submit each question as they go along to get immediate feedback, and if they do not get it right immediately, have another try for fewer marks. That would be \'Interactive with multiple tries\' mode.';
 $string['importfromcoursefiles'] = '... or choose a course file to import.';
 $string['importfromupload'] = 'Select a file to upload ...';
 $string['incorrect'] = 'Incorrect';
@@ -284,8 +285,8 @@ $string['manualgradeoutofrange'] = 'This grade is outside the valid range.';
 $string['manuallygraded'] = 'Manually graded {$a->mark} with comment: {$a->comment}';
 $string['mark'] = 'Mark';
 $string['markedoutof'] = 'Marked out of';
-$string['markedoutofmax'] = 'Marked out of $a';
-$string['markoutofmax'] = 'Mark $a->mark out of $a->max';
+$string['markedoutofmax'] = 'Marked out of {$a}';
+$string['markoutofmax'] = 'Mark {$a->mark} out of {$a->max}';
 $string['marks'] = 'Marks';
 $string['noresponse'] = '[No response]';
 $string['notanswered'] = 'Not answered';
@@ -301,7 +302,7 @@ $string['penaltyforeachincorrecttry'] = 'Penalty for each incorrect try';
 $string['penaltyforeachincorrecttry_help'] = 'When you run your questions using the \'Interactive with multiple tries\' or \'Adaptive mode\' behaviour, so that the the student will have several tries to get the question right, then this option controls how much they are penalised for each incorrect try.
 
 The penalty is a proportion of the total question grade, so if the question is worth three marks, and the penalty is 0.3333333, then the student will score 3 if they get the question right first time, 2 if they get it right second try, and 1 of they get it right on the third try.';
-$string['previewquestion'] = 'Preview question: $a';
+$string['previewquestion'] = 'Preview question: {$a}';
 $string['questionbehaviouradminsetting'] = 'Question behaviour settings';
 $string['questionbehavioursdisabled'] = 'Question behaviours to disable';
 $string['questionbehavioursdisabledexplained'] = 'Enter a comma separated list of behaviours you do not want to appear in dropdown menu';
@@ -309,7 +310,7 @@ $string['questionbehavioursorder'] = 'Question behaviours order';
 $string['questionbehavioursorderexplained'] = 'Enter a comma separated list of behaviours in the order you want them to appear in dropdown menu';
 $string['questionidmismatch'] = 'Question ids mismatch';
 $string['questionname'] = 'Question name';
-$string['questionx'] = 'Question $a';
+$string['questionx'] = 'Question {$a}';
 $string['questiontext'] = 'Question text';
 $string['requiresgrading'] = 'Requires grading';
 $string['responsehistory'] = 'Response history';
@@ -332,8 +333,8 @@ $string['submissionoutofsequencefriendlymessage'] = "You have entered data outsi
 $string['submit'] = 'Submit';
 $string['submitandfinish'] = 'Submit and finish';
 $string['submitted'] = 'Submit: {$a}';
-$string['unknownquestion'] = 'Unknown question: $a.';
-$string['unknownquestioncatregory'] = 'Unknown question category: $a.';
+$string['unknownquestion'] = 'Unknown question: {$a}.';
+$string['unknownquestioncatregory'] = 'Unknown question category: {$a}.';
 $string['whethercorrect'] = 'Whether correct';
-$string['xoutofmax'] = '$a->mark out of $a->max';
+$string['xoutofmax'] = '{$a->mark} out of {$a->max}';
 $string['yougotnright'] = 'You have correctly selected {$a->num}.';
index 3793083..9b1149e 100644 (file)
@@ -849,7 +849,7 @@ function question_load_questions($questionids, $extrafields = '', $join = '') {
  * @param boolean $loadtags load the question tags from the tags table. Optional, default false.
  * @return boolean true if successful, else false.
  */
-function _tidy_question(&$question, $loadtags = false) {
+function _tidy_question($question, $loadtags = false) {
     global $CFG, $QTYPES;
     if (!array_key_exists($question->qtype, $QTYPES)) {
         $question->qtype = 'missingtype';
index a4d4730..97bc996 100755 (executable)
@@ -4,6 +4,8 @@ function xmldb_local_qedatabase_install() {
     global $DB;
     $dbman = $DB->get_manager();
 
+    // TODO quiz default settings are now in config_plugins.
+
     // Bit of a hack to prevent errors like "Cannot downgrade local_qedatabase from ... to ...".
     $oldversion = 2008000000;
     $DB->set_field('config_plugins', 'value', $oldversion,
index 0eba902..ccf9162 100644 (file)
@@ -126,7 +126,8 @@ abstract class question_behaviour {
      * @return qbehaviour_renderer get the appropriate renderer to use for this model.
      */
     public function get_renderer() {
-        return renderer_factory::get_renderer(get_class($this));
+        global $PAGE;
+        return $PAGE->get_renderer(get_class($this));
     }
 
     /**
index 8fed6af..d58b76f 100644 (file)
@@ -138,15 +138,14 @@ abstract class question_bank {
      * @return question_definition loaded from the database.
      */
     public static function load_question($questionid) {
+        global $DB;
+
         if (self::$testmode) {
             // Evil, test code in production, but now way round it.
             return self::return_test_question_data($questionid);
         }
 
-        $questiondata = get_record('question', 'id', $questionid);
-        if (empty($questiondata)) {
-            throw new Exception('Unknown question id ' . $questionid);
-        }
+        $questiondata = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
         get_question_options($questiondata);
         return self::make_question($questiondata);
     }
@@ -223,7 +222,8 @@ class question_finder {
         if ($extraconditions) {
             $extraconditions = ' AND (' . $extraconditions . ')';
         }
-        $questionids = get_records_select_menu('question',
+        // TODO switch to using $DB->in_or_equal.
+        $questionids = $DB->get_records_select_menu('question',
                 "category IN ($categoryids)
                  AND parent = 0
                  AND hidden = 0
index 960867f..36e2297 100644 (file)
@@ -46,7 +46,6 @@ class question_engine_data_mapper {
     public function __construct($db = null) {
         if (is_null($db)) {
             global $DB;
-            new moodle_database;
             $this->db = $DB;
         } else {
             $this->db = $db;
@@ -254,7 +253,7 @@ WHERE
 ORDER BY
     qa.slot,
     qas.sequencenumber
-    ", array('qubaid', $qubaid));
+    ", array('qubaid' => $qubaid));
 
         if (!$records) {
             throw new Exception('Failed to load questions_usage_by_activity ' . $qubaid);
@@ -272,7 +271,7 @@ ORDER BY
      * @return array of records. See the SQL in this function to see the fields available.
      */
     public function load_questions_usages_latest_steps(qubaid_condition $qubaids, $slots) {
-        list($slottest, $params) = get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
+        list($slottest, $params) = $this->db->get_in_or_equal($slots, SQL_PARAMS_NAMED, 'slot0000');
 
         $records = get_records_sql("
 SELECT
@@ -595,7 +594,7 @@ ORDER BY
         $record->component = addslashes($quba->get_owning_component());
         $record->preferredbehaviour = addslashes($quba->get_preferred_behaviour());
 
-        if (!update_record('question_usages', $record)) {
+        if (!$this->db->update_record('question_usages', $record)) {
             throw new Exception('Failed to update question_usage_by_activity ' . $record->id);
         }
     }
@@ -606,6 +605,8 @@ ORDER BY
      * @param question_attempt $qa the question attempt that has changed.
      */
     public function update_question_attempt(question_attempt $qa) {
+        global $DB;
+
         $record = new stdClass;
         $record->id = $qa->get_database_id();
         $record->maxmark = $qa->get_max_mark();
@@ -616,7 +617,7 @@ ORDER BY
         $record->responsesummary = addslashes($qa->get_response_summary());
         $record->timemodified = time();
 
-        if (!update_record('question_attempts', $record)) {
+        if (!$this->db->update_record('question_attempts', $record)) {
             throw new Exception('Failed to update question_attempt ' . $record->id);
         }
     }
@@ -627,25 +628,26 @@ ORDER BY
      * database.
      * @param string $where a where clause. Becuase of MySQL limitations, you
      *      must refer to {$CFG->prefix}question_usages.id in full like that.
+     * @param array $params values to substitute for placeholders in $where.
      */
-    public function delete_questions_usage_by_activities($where) {
+    public function delete_questions_usage_by_activities($where, $params) {
         global $CFG;
-        delete_records_select('question_attempt_step_data', "attemptstepid IN (
+        $this->db->delete_records_select('question_attempt_step_data', "attemptstepid IN (
                 SELECT qas.id
-                FROM {$CFG->prefix}question_attempts qa
-                JOIN {$CFG->prefix}question_attempt_steps qas ON qas.questionattemptid = qa.id
-                JOIN {$CFG->prefix}question_usages ON qa.questionusageid = {$CFG->prefix}question_usages.id
-                WHERE $where)");
-        delete_records_select('question_attempt_steps', "questionattemptid IN (
+                FROM {question_attempts} qa
+                JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
+                JOIN {question_usages} ON qa.questionusageid = {question_usages}.id
+                WHERE $where)", $params);
+        $this->db->delete_records_select('question_attempt_steps', "questionattemptid IN (
                 SELECT qa.id
-                FROM {$CFG->prefix}question_attempts qa
-                JOIN {$CFG->prefix}question_usages ON qa.questionusageid = {$CFG->prefix}question_usages.id
-                WHERE $where)");
-        delete_records_select('question_attempts', "questionusageid IN (
+                FROM {question_attempts} qa
+                JOIN {question_usages} ON qa.questionusageid = {question_usages}.id
+                WHERE $where)", $params);
+        $this->db->delete_records_select('question_attempts', "questionusageid IN (
                 SELECT id
-                FROM {$CFG->prefix}question_usages
-                WHERE $where)");
-        delete_records_select('question_usages', $where);
+                FROM {question_usages}
+                WHERE $where)", $params);
+        $this->db->delete_records_select('question_usages', $where, $params);
     }
 
     /**
@@ -658,11 +660,11 @@ ORDER BY
             return;
         }
         list($test, $params) = get_in_or_equal($qaids);
-        delete_records_select('question_attempt_step_data', "attemptstepid IN (
+        $this->db->delete_records_select('question_attempt_step_data', "attemptstepid IN (
                 SELECT qas.id
-                FROM {$CFG->prefix}question_attempt_steps qas
-                WHERE questionattemptid $test)");
-        delete_records_select('question_attempt_steps', 'questionattemptid ' . $test);
+                FROM {question_attempt_steps} qas
+                WHERE questionattemptid $test)", $params);
+        $this->db->delete_records_select('question_attempt_steps', 'questionattemptid ' . $test, $params);
     }
 
     /**
@@ -671,18 +673,17 @@ ORDER BY
      */
     public function delete_previews($questionid) {
         global $CFG;
-        $previews = get_records_sql_menu("
+        $previews = $this->db->get_records_sql_menu("
                 SELECT DISTINCT quba.id, 1
-                FROM {$CFG->prefix}question_usages quba
-                JOIN {$CFG->prefix}question_attempts qa ON qa.questionusageid = quba.id
+                FROM {question_usages} quba
+                JOIN {question_attempts} qa ON qa.questionusageid = quba.id
                 WHERE quba.component = 'core_question_preview' AND
-                    qa.questionid = '$questionid'");
+                    qa.questionid = ?", array($questionid));
         if (empty($previews)) {
             return;
         }
-        $this->delete_questions_usage_by_activities(
-                "{$CFG->prefix}question_usages.id IN (" .
-                implode(',', array_keys($previews)) . ')');
+        list($test, $params) = $this->db->get_in_or_equal(array_keys($previews));
+        $this->delete_questions_usage_by_activities('question_usages.id ' . $test, $params);
     }
 
     /**
@@ -725,7 +726,8 @@ ORDER BY
      */
     public function in_summary_state_test($summarystate, $equal = true) {
         $states = question_state::get_all_for_summary_state($summarystate);
-        list($sql, $params) = get_in_or_equal($states, SQL_PARAMS_QM, 'param0000', $equal);
+        list($sql, $params) = $this->db->get_in_or_equal($states, SQL_PARAMS_QM, 'param0000', $equal);
+        // TODO return $params;
         return $sql;
     }
 
@@ -737,8 +739,9 @@ ORDER BY
      * @param number $newmaxmark the new max mark to set.
      */
     public function set_max_mark_in_attempts(qubaid_condition $qubaids, $slot, $newmaxmark) {
-        set_field_select('question_attempts', 'maxmark', $newmaxmark,
-                "questionusageid {$qubaids->usage_id_in()} AND slot = $slot");
+        $this->db->set_field_select('question_attempts', 'maxmark', $newmaxmark,
+                "questionusageid {$qubaids->usage_id_in()} AND slot = :slot",
+                $qubaids->usage_id_in_params() + array('slot' => $slot));
     }
 
     /**
@@ -754,23 +757,22 @@ ORDER BY
      * @return string SQL code for the subquery.
      */
     public function sum_usage_marks_subquery($qubaid) {
-        global $CFG;
         return "SELECT SUM(qa.maxmark * qas.fraction)
-            FROM {$CFG->prefix}question_attempts qa
+            FROM {question_attempts} qa
             JOIN (
                 SELECT summarks_qa.id AS questionattemptid, MAX(summarks_qas.id) AS latestid
-                FROM {$CFG->prefix}question_attempt_steps summarks_qas
-                JOIN {$CFG->prefix}question_attempts summarks_qa ON summarks_qa.id = summarks_qas.questionattemptid
+                FROM {question_attempt_steps} summarks_qas
+                JOIN {question_attempts} summarks_qa ON summarks_qa.id = summarks_qas.questionattemptid
                 WHERE summarks_qa.questionusageid = $qubaid
                 GROUP BY summarks_qa.id
             ) lateststepid ON lateststepid.questionattemptid = qa.id
-            JOIN {$CFG->prefix}question_attempt_steps qas ON qas.id = lateststepid.latestid
+            JOIN {question_attempt_steps} qas ON qas.id = lateststepid.latestid
             WHERE qa.questionusageid = $qubaid
             HAVING COUNT(CASE WHEN qas.state = 'needsgrading' THEN 1 ELSE NULL END) = 0";
+        // TODO handle $qubaid with placeholders.
     }
 
     public function question_attempt_latest_state_view($alias) {
-        global $CFG;
         return "(
                 SELECT
                     {$alias}qa.id AS questionattemptid,
@@ -802,7 +804,7 @@ ORDER BY
         global $CFG;
         return "(
                 SELECT MAX(id)
-                FROM {$CFG->prefix}question_attempt_steps
+                FROM {question_attempt_steps}
                 WHERE questionattemptid = $questionattemptid
             )";
     }
@@ -812,8 +814,9 @@ ORDER BY
      * @return boolean whether any of these questions are being used by the question engine.
      */
     public static function questions_in_use(array $questionids) {
-        return record_exists_select('question_attempts', 'questionid IN (' .
-                implode(',', $questionids) . ')');
+        list($test, $params) = $this->db->get_in_or_equal($questionids);
+        return $this->db->record_exists_select('question_attempts',
+                'questionid ' . $test, $params);
     }
 }
 
index 2ef242f..7fb9296 100644 (file)
@@ -96,16 +96,16 @@ abstract class question_engine {
      */
     public static function delete_questions_usage_by_activity($qubaid) {
         global $CFG;
-        self::delete_questions_usage_by_activities($CFG->prefix . 'question_usages.id = ' . $qubaid);
+        self::delete_questions_usage_by_activities('{question_usages}.id = :qubaid', array('qubaid' => $qubaid));
     }
 
     /**
      * Delete a {@link question_usage_by_activity} from the database, based on its id.
      * @param integer $qubaid the id of the usage to delete.
      */
-    public static function delete_questions_usage_by_activities($where) {
+    public static function delete_questions_usage_by_activities($where, $params) {
         $dm = new question_engine_data_mapper();
-        $dm->delete_questions_usage_by_activities($where);
+        $dm->delete_questions_usage_by_activities($where, $params);
     }
 
     /**
@@ -291,7 +291,7 @@ abstract class question_engine {
         $archetypes = self::get_archetypal_behaviours();
 
         // If no admin setting return all behavious
-        if (!$CFG->questionbehavioursdisabled && !$CFG->questionbehavioursorder) {
+        if (empty($CFG->questionbehavioursdisabled) && empty($CFG->questionbehavioursorder)) {
             return $archetypes;
         }
 
@@ -1797,7 +1797,8 @@ class question_attempt {
      * @return string HTML fragment representing the question.
      */
     public function render($options, $number) {
-        $qoutput = renderer_factory::get_renderer('core', 'question');
+        global $PAGE;
+        $qoutput = $PAGE->get_renderer('core', 'question');
         $qtoutput = $this->question->get_renderer();
         return $this->behaviour->render($options, $number, $qoutput, $qtoutput);
     }
index 856b3cb..266c5b8 100644 (file)
@@ -1,4 +1,21 @@
 <?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 page displays a preview of a question
  *
  * information is stored in the session as an array of subsequent states rather
  * than in the database.
  *
- * TODO: make this work with activities other than quiz
- *
- * @author Alex Smith as part of the Serving Mathematics project
- *         {@link http://maths.york.ac.uk/serving_maths}
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package questionbank
+ * @package moodlecore
+ * @subpackage questionbank
+ * @copyright Alex Smith {@link http://maths.york.ac.uk/serving_maths} and
+ *      numerous contributors.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-    require_once("../config.php");
-    require_once($CFG->libdir.'/questionlib.php');
-    require_once($CFG->dirroot.'/mod/quiz/locallib.php'); // We really want to get rid of this
-
-    $id = required_param('id', PARAM_INT);        // question id
-    // if no quiz id is specified then a dummy quiz with default options is used
-    $quizid = optional_param('quizid', 0, PARAM_INT);
-    // if no quiz id is specified then tell us the course
-
-    $pageurl = new moodle_url('/question/preview.php', array('id' => $id, 'continue' => 1));
-    if (empty($quizid)) {
-        $courseid = required_param('courseid', PARAM_INT);
-        $pageurl->param('courseid', $courseid);
-    } else {
-        $pageurl->param('quizid', $quizid);
-    }
-    $PAGE->set_url($pageurl);
-    $PAGE->set_pagelayout('popup');
-
-    // Test if we are continuing an attempt at a question
-    $continue = optional_param('continue', 0, PARAM_BOOL);
-    // Check for any of the submit buttons
-    $fillcorrect = optional_param('fillcorrect', 0, PARAM_BOOL);
-    $markall = optional_param('markall', 0, PARAM_BOOL);
-    $finishattempt = optional_param('finishattempt', 0, PARAM_BOOL);
-    $back = optional_param('back', 0, PARAM_BOOL);
-    $startagain = optional_param('startagain', 0, PARAM_BOOL);
-    // We are always continuing an attempt if a submit button was pressed with the
-    // exception of the start again button
-    if ($fillcorrect || $markall || $finishattempt || $back) {
-        $continue = true;
-    } else if ($startagain) {
-        $continue = false;
-    }
-
-    $url = new moodle_url('/question/preview.php');
-    $url->param('id', $id);
-    if ($quizid) {
-        $url->param('quizid', $quizid);
-    } else {
-        $url->param('courseid', $courseid);
-    }
-    $url->param('continue', 1);
-    if (!$continue) {
-        // Start a new attempt; delete the old session
-        unset($SESSION->quizpreview);
-        // Redirect to ourselves but with continue=1; prevents refreshing the page
-        // from restarting an attempt (needed so that random questions don't change)
-        redirect($url);
-    }
-    // Load the question information
-    if (!$questions = $DB->get_records('question', array('id' =>  $id))) {
-        print_error('cannotloadquestion', 'question');
-    }
-    if (empty($quizid)) {
-        $quiz = new cmoptions;
-        $quiz->id = 0;
-        $quiz->review = get_config('quiz', 'review');
-        require_login($courseid, false);
-        $quiz->course = $courseid;
-        $quiz->decimalpoints = get_config('quiz', 'decimalpoints');
-        $context = get_context_instance(CONTEXT_COURSE, $courseid);
-    } else if (!$quiz = $DB->get_record('quiz', array('id' => $quizid))) {
-        print_error('invalidquizid', 'quiz', '', $quizid);
-    } else {
-        $cm = get_coursemodule_from_instance('quiz', $quizid, $quiz->course);
-        require_login($quiz->course, false, $cm);
-        $context = get_context_instance(CONTEXT_MODULE, $cm->id);
-    }
-
-    if ($maxgrade = $DB->get_field('quiz_question_instances', 'grade', array('quiz' => $quiz->id, 'question' => $id))) {
-        $questions[$id]->maxgrade = $maxgrade;
-    } else {
-        $questions[$id]->maxgrade = $questions[$id]->defaultgrade;
-    }
-
-    $quiz->id = 0; // just for safety
-    $quiz->questions = $id;
-
-    if (!$category = $DB->get_record("question_categories", array("id" => $questions[$id]->category))) {
-        print_error('invalidquestionid', 'quiz');
-    }
-
-    if (!question_has_capability_on($questions[$id], 'use', $questions[$id]->category)){
-        print_error('cannotpreview', 'question');
-    }
-    if (isset($COURSE)){
-        $quiz->course = $COURSE->id;
-    }
+require_once(dirname(__FILE__) . '/../config.php');
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->libdir . '/formslib.php');
+require_once(dirname(__FILE__) . '/previewlib.php');
+
+// Get and validate question id.
+$id = required_param('id', PARAM_INT);
+$question = question_bank::load_question($id);
+require_login();
+$category = $DB->get_record('question_categories', array('id' => $question->category), '*', MUST_EXIST);
+question_require_capability_on($question, 'use');
+$PAGE->set_pagelayout('popup');
+$PAGE->set_context(get_context_instance_by_id($category->contextid));
+
+// Get and validate display options.
+$options = new question_preview_options($question);
+$options->load_user_defaults();
+$options->set_from_request();
+$PAGE->set_url(question_preview_url($id, $options->behaviour, $options->maxmark, $options));
+
+// Get and validate exitsing preview, or start a new one.
+$previewid = optional_param('previewid', 0, PARAM_ALPHANUM);
+if ($previewid) {
+    if (!isset($SESSION->question_previews[$previewid])) {
+        print_error('notyourpreview', 'question');
+    }
+    try {
+        $quba = question_engine::load_questions_usage_by_activity($previewid);
+    } catch (Exception $e){
+        print_error('submissionoutofsequencefriendlymessage', 'question',
+                question_preview_url($question->id, $options->behaviour,
+                $options->maxmark, $options), null, $e);
+    }
+    $slot = $quba->get_first_question_number();
+    $usedquestion = $quba->get_question($slot);
+    if ($usedquestion->id != $question->id) {
+        print_error('questionidmismatch', 'question');
+    }
+    $question = $usedquestion;
+
+} else {
+    $quba = question_engine::make_questions_usage_by_activity('core_question_preview',
+            get_context_instance_by_id($category->contextid));
+    $quba->set_preferred_behaviour($options->behaviour);
+    $slot = $quba->add_question($question, $options->maxmark);
+    $quba->start_all_questions();
+
+    $transaction = $DB->start_delegated_transaction();
+    question_engine::save_questions_usage_by_activity($quba);
+    $transaction->allow_commit();
+
+    $SESSION->question_previews[$quba->get_id()] = true;
+}
+$options->behaviour = $quba->get_preferred_behaviour();
+$options->maxmark = $quba->get_question_max_mark($slot);
+
+// Create the settings form, and initialise the fields.
+$optionsform = new preview_options_form($CFG->wwwroot . '/question/preview.php?id=' . $question->id, $quba);
+$optionsform->set_data($options);
+
+// Process change of settings, if that was requested.
+if ($newoptions = $optionsform->get_submitted_data()) {
+    // Set user preferences
+    $options->save_user_preview_options($newoptions);
+    restart_preview($previewid, $question->id, $newoptions);
+}
+
+// Prepare a URL that is used in various places.
+$actionurl = question_preview_action_url($question->id, $quba->get_id(), $options);
+
+// Process any actions from the buttons at the bottom of the form.
+if (data_submitted() && confirm_sesskey()) {
+    if (optional_param('restart', false, PARAM_BOOL)) {
+        restart_preview($previewid, $question->id, $options);
+
+    } else if (optional_param('fill', null, PARAM_BOOL)) {
+        $correctresponse = $quba->get_correct_response($slot);
+        $quba->process_action($slot, $correctresponse);
+
+        $transaction = $DB->start_delegated_transaction();
+        question_engine::save_questions_usage_by_activity($quba);
+        $transaction->allow_commit();
+
+        redirect($actionurl);
+
+    } else if (optional_param('finish', null, PARAM_BOOL)) {
+        try {
+            $quba->process_all_actions();
+        } catch (question_out_of_sequence_exception $e){
+            print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
+        }
+        $quba->finish_all_questions();
 
-    // Load the question type specific information
-    if (!get_question_options($questions)) {
-        print_error('newattemptfail', 'quiz');
-    }
+        $transaction = $DB->start_delegated_transaction();
+        question_engine::save_questions_usage_by_activity($quba);
+        $transaction->allow_commit();
+        redirect($actionurl);
 
-    // Create a dummy quiz attempt
-    // TODO: find out what of the following we really need. What is $attempt
-    //       really used for?
-    $timenow = time();
-    $attempt->quiz = $quiz->id;
-    $attempt->userid = $USER->id;
-    $attempt->attempt = 0;
-    $attempt->sumgrades = 0;
-    $attempt->timestart = $timenow;
-    $attempt->timefinish = 0;
-    $attempt->timemodified = $timenow;
-    $attempt->uniqueid = 0;
-    $attempt->id = 0;
-    $attempt->layout = $id;
-
-    // Restore the history of question sessions from the moodle session or create
-    // new sessions. Make $states a reference to the states array in the moodle
-    // session.
-    if (isset($SESSION->quizpreview->states) and $SESSION->quizpreview->questionid == $id) {
-        // Reload the question session history from the moodle session
-        $states =& $SESSION->quizpreview->states;
-        $historylength = count($states) - 1;
-        if ($back && $historylength > 0) {
-            // Go back one step in the history
-            unset($states[$historylength]);
-            $historylength--;
-        }
     } else {
-        // Record the question id in the moodle session
-        $SESSION->quizpreview->questionid = $id;
-        // Create an empty session for the question
-        if (!$newstates = get_question_states($questions, $quiz, $attempt)) {
-            print_error('newattemptfail', 'quiz');
-        }
-        $newstates[$id]->questionsessionid = 0;
-        $SESSION->quizpreview->states = array($newstates);
-        $states =& $SESSION->quizpreview->states;
-        $historylength = 0;
-    }
-
-    if (!$fillcorrect && !$back && ($form = data_submitted())) {
-        $form = (array)$form;
-        $submitted = true;
-
-        // Create a new item in the history of question states (don't simplify!)
-        $states[$historylength + 1] = array();
-        $states[$historylength + 1][$id] = clone($states[$historylength][$id]);
-        $historylength++;
-        $curstate =& $states[$historylength][$id];
-        $curstate->changed = false;
-
-        // Process the responses
-        unset($form['id']);
-        unset($form['quizid']);
-        unset($form['continue']);
-        unset($form['markall']);
-        unset($form['finishattempt']);
-        unset($form['back']);
-        unset($form['startagain']);
-
-        if ($finishattempt) {
-            $event = QUESTION_EVENTCLOSE;
-        } else if ($markall) {
-            $event = QUESTION_EVENTSUBMIT;
-        } else {
-            $event = QUESTION_EVENTSAVE;
-        }
-        if ($actions = question_extract_responses($questions, $form, $event)) {
-            $actions[$id]->timestamp = 0; // We do not care about timelimits here
-            if (!question_process_responses($questions[$id], $curstate, $actions[$id], $quiz, $attempt)) {
-                unset($SESSION->quizpreview);
-                print_error('errorprocessingresponses', 'question', $url->out());
-            }
-            if (!$curstate->changed) {
-                // Update the current state rather than creating a new one
-                $historylength--;
-                unset($states[$historylength]);
-                $states = array_values($states);
-                $curstate =& $states[$historylength][$id];
-            }
+        try {
+            $quba->process_all_actions();
+        } catch (question_out_of_sequence_exception $e){
+            print_error('submissionoutofsequencefriendlymessage', 'question', $actionurl);
         }
-    } else {
-        $submitted = false;
-        $curstate =& $states[$historylength][$id];
-    }
-
-    // TODO: should not use quiz-specific function here
-    $options = quiz_get_renderoptions($quiz, $attempt, $context, $curstate);
-    $options->noeditlink = true;
 
-    // Fill in the correct responses (unless the question is in readonly mode)
-    if ($fillcorrect && !$options->readonly) {
-        $curstate->responses = $QTYPES[$questions[$id]->qtype]->get_correct_responses($questions[$id], $curstate);
-    }
+        $transaction = $DB->start_delegated_transaction();
+        question_engine::save_questions_usage_by_activity($quba);
+        $transaction->allow_commit();
 
-    $strpreview = get_string('preview', 'quiz').' '.format_string($questions[$id]->name);
-    $questionlist = array($id);
-    question_get_html_head_contributions($questionlist, $questions, $states[$historylength]);
-    $PAGE->set_title($strpreview);
-    $PAGE->set_heading($COURSE->fullname);
-    echo $OUTPUT->header();
-
-    if (!empty($quizid)) {
-        echo '<p class="quemodname">'.get_string('modulename', 'quiz') . ': ';
-        p(format_string($quiz->name));
-        echo "</p>\n";
-    }
-    $number = 1;
-    echo '<form method="post" action="'.$url->out_omit_querystring().'" enctype="multipart/form-data" id="responseform">', "\n";
-    $PAGE->requires->js_init_call('M.core_question_engine.init_form', array('#responseform'));
-
-    print_question($questions[$id], $curstate, $number, $quiz, $options, $context);
-
-    echo '<div class="controls">';
-    echo html_writer::input_hidden_params($url);
-
-    // Print the mark and finish attempt buttons
-    echo '<input name="markall" type="submit" value="' . get_string('markall',
-     'quiz') . "\" />\n";
-    echo '<input name="finishattempt" type="submit" value="' .
-     get_string('submitallandfinish', 'quiz') . "\" />\n";
-    echo '<br />';
-    echo '<br />';
-    // Print the fill correct button (unless the question is in readonly mode)
-    if (!$options->readonly) {
-        echo '<input name="fillcorrect" type="submit" value="' .
-         get_string('fillcorrect', 'quiz') . "\" />\n";
-    }
-    // Print the navigation buttons
-    if ($historylength > 0) {
-        echo '<input name="back" type="submit" value="' . get_string('previous',
-         'quiz') . "\" />\n";
-    }
-    // Print the start again button
-    echo '<input name="startagain" type="submit" value="' .
-     get_string('startagain', 'quiz') . "\" />\n";
-    // Print the close window button
-    echo '<input type="button" onclick="window.close()" value="' .
-     get_string('closepreview', 'quiz') . "\" />";
-    echo '</div>';
-    echo '</form>';
-    echo $OUTPUT->footer();
+        $scrollpos = optional_param('scrollpos', '', PARAM_RAW);
+        if ($scrollpos !== '') {
+            $actionurl .= '&scrollpos=' . ((int) $scrollpos);
+        }
+        redirect($actionurl);
+    }
+}
+
+if ($question->length) {
+    $displaynumber = '1';
+} else {
+    $displaynumber = 'i';
+}
+$restartdisabled = '';
+$finishdisabled = '';
+$filldisabled = '';
+if ($quba->get_question_state($slot)->is_finished()) {
+    $finishdisabled = ' disabled="disabled"';
+    $filldisabled = ' disabled="disabled"';
+}
+if (!$previewid) {
+    $restartdisabled = ' disabled="disabled"';
+}
+
+$PAGE->requires->js_init_call('M.core_question_preview.init', null, array(
+        'name' => 'core_question_preview',
+        'fullpath' => '/question/preview.js',
+        'requires' => array('base', 'dom', 'event-delegate', 'event-key', 'core_question_engine'),
+        'strings' => array(
+            array('question', 'closepreview'),
+        )));
+
+// Output
+$title = get_string('previewquestion', 'question', format_string($question->name));
+$headtags = question_engine::initialise_js() . $quba->render_question_head_html($slot);
+$PAGE->set_title($title);
+$PAGE->set_heading($title);
+echo $OUTPUT->header();
+
+// Start the question form.
+echo '<form method="post" action="' . s($actionurl) .
+        '" enctype="multipart/form-data" id="responseform">', "\n";
+echo '<input type="hidden" name="sesskey" value="' . sesskey() . '" />', "\n";
+echo '<input type="hidden" name="slots" value="' . $slot . '" />', "\n";
+
+// Output the question.
+echo $quba->render_question($slot, $options, $displaynumber);
+
+echo '<p class="notifytiny">' . get_string('behaviourbeingused', 'question',
+        question_engine::get_behaviour_name(
+        $quba->get_question_attempt($slot)->get_behaviour_name())) . '</p>';
+// Finish the question form.
+echo '<div id="previewcontrols" class="controls">';
+echo '<input type="submit" name="restart"' . $restartdisabled .
+        ' value="' . get_string('restart', 'question') . '" />', "\n";
+echo '<input type="submit" name="fill"' . $filldisabled .
+        ' value="' . get_string('fillincorrect', 'question') . '" />', "\n";
+echo '<input type="submit" name="finish"' . $finishdisabled .
+        ' value="' . get_string('submitandfinish', 'question') . '" />', "\n";
+echo '<input type="hidden" name="scrollpos" id="scrollpos" value="" />';
+echo '</div>';
+echo '</form>';
+
+// Display the settings form.
+$optionsform->display();
+
+echo '<script type="text/javascript">question_preview_init("' .
+        get_string('closepreview', 'question') . '", "previewcontrols");</script>', "\n";
+
+echo $OUTPUT->footer();
 
index f56f84a..273ad58 100644 (file)
@@ -45,7 +45,7 @@ class preview_options_form extends moodleform {
 
         $behaviours = question_engine::get_behaviour_options($this->_customdata->get_preferred_behaviour());
         $mform->addElement('select', 'behaviour', get_string('howquestionsbehave', 'question'), $behaviours);
-        $mform->setHelpButton('behaviour', array('howquestionsbehave', get_string('howquestionsbehave', 'question'), 'question'));
+        $mform->addHelpButton('behaviour', 'howquestionsbehave', 'question');
 
         $mform->addElement('text', 'maxmark', get_string('markedoutof', 'question'), array('size' => '5'));
         $mform->setType('maxmark', PARAM_NUMBER);
@@ -101,7 +101,7 @@ class question_preview_options extends question_display_options {
         $this->maxmark = $question->defaultmark;
         $this->correctness = self::VISIBLE;
         $this->marks = self::MARK_AND_MAX;
-        $this->markdp = $CFG->quiz_decimalpoints;
+        $this->markdp = get_config('quiz', 'decimalpoints');
         $this->feedback = self::VISIBLE;
         $this->numpartscorrect = $this->feedback;
         $this->generalfeedback = self::VISIBLE;
@@ -289,10 +289,12 @@ function question_preview_action_url($questionid, $qubaid,
  * @param object $displayoptions
  */
 function restart_preview($previewid, $questionid, $displayoptions) {
+    global $DB;
+
     if ($previewid) {
-        begin_sql();
+        $transaction = $DB->start_delegated_transaction();
         question_engine::delete_questions_usage_by_activity($previewid);
-        commit_sql();
+        $transaction->allow_commit();
     }
     redirect(question_preview_url($questionid, $displayoptions->behaviour, $displayoptions->maxmark, $displayoptions));
 }
index d6adaed..4eb7396 100644 (file)
@@ -74,10 +74,10 @@ DONE lib/questionlib.php                                | 1434 ++--------
  mod/quiz/config.html                               |  304 ++-
  mod/quiz/db/access.php                             |   19 +-
  mod/quiz/db/install.xml                            |  191 +-
- mod/quiz/db/mysql.php                              | 1163 --------
- mod/quiz/db/postgres7.php                          | 1497 ----------
- mod/quiz/db/upgrade.php                            | 1204 ++++++++-
- mod/quiz/defaults.php                              |   12 +-
+DONE mod/quiz/db/mysql.php                              | 1163 --------
+DONE mod/quiz/db/postgres7.php                          | 1497 ----------
+DONE mod/quiz/db/upgrade.php                            | 1204 ++++++++-
+DONE mod/quiz/defaults.php                              |   12 +-
  mod/quiz/edit.js                                   |  131 +
  mod/quiz/edit.php                                  |  118 +-
  mod/quiz/editlib.php                               |   79 +-
@@ -172,13 +172,34 @@ DONE question/file.php                                  |  171 +- | but this fil
  question/import_form.php                           |   19 +  | the change is to add validation that a file has been uploaded.
 DONE question/move_form.php                             |   32 +-
 DONE question/preview.js                                |   47 +
- question/preview.php                               |  408 ++--
+DONE question/preview.php                               |  408 ++--
 DONE question/previewlib.php                            |  214 ++
 DONE question/qengine.js                                |  181 ++
 DONE question/question.php                              |    3 +-
  question/restorelib.php                            |   88 +-
 DONE question/toggleflag.php                            |   49 +
 
+DONE question/engine/bank.php                           |  221 ++
+DONE question/engine/compatibility.php                  |  958 +++++++
+DONE question/engine/datalib.php                        | 1069 ++++++++
+DONE question/engine/lib.php                            | 2873 ++++++++++++++++++++
+DONE question/engine/renderer.php                       |  362 +++
+DONE question/engine/simpletest/helpers.php             |  629 +++++
+DONE question/engine/simpletest/testdatalib.php         |  125 +
+DONE question/engine/simpletest/testhtmlwriter.php      |   94 +
+DONE question/engine/simpletest/testquestionattempt.php |  346 +++
+DONE question/engine/simpletest/testquestionattemptiterator.php     |  108 +
+DONE question/engine/simpletest/testquestionattemptstep.php  |  170 ++
+DONE question/engine/simpletest/testquestionattemptstepiterator.php |  127 +
+DONE question/engine/simpletest/testquestioncbm.php     |   40 +
+DONE question/engine/simpletest/testquestionengine.php  |   94 +
+DONE question/engine/simpletest/testquestionstate.php   |  154 ++
+DONE question/engine/simpletest/testquestionusagebyactivity.php     |  153 ++
+DONE question/engine/simpletest/testquestionutils.php   |  186 ++
+DONE question/engine/states.php                         |  460 ++++
+DONE question/engine/todo.txt                           |  235 ++
+ question/engine/upgradefromoldqe/upgrade.php       | 1963 +++++++++++++
+
 DONE question/type/edit_question_form.php               |  264 ++-
 DONE question/type/question.html                        |   46 -
 DONE question/type/questionbase.php                     |  787 ++++++
@@ -192,14 +213,14 @@ DONE question/type/truefalse/display.html               |   30 -
 DONE question/type/truefalse/edit_truefalse_form.php    |   53 +-
 DONE question/type/truefalse/lang/en_utf8/qtype_truefalse.php     |   14 +
 DONE question/type/truefalse/question.php               |   97 +
- question/type/truefalse/questiontype.php           |  212 +-
+DONE question/type/truefalse/questiontype.php           |  212 +-
 DONE question/type/truefalse/renderer.php               |  142 +
 DONE question/type/truefalse/simpletest/testquestion.php     |   99 +
 DONE question/type/truefalse/simpletest/testquestiontype.php |   73 +
 DONE question/type/truefalse/version.php                |    4 +-
 
- question/behaviour/behaviourbase.php               |  627 +++++
- question/behaviour/rendererbase.php                |  200 ++
+DONE question/behaviour/behaviourbase.php               |  627 +++++
+DONE question/behaviour/rendererbase.php                |  200 ++
 
  question/behaviour/adaptive/behaviour.php          |  181 ++
  question/behaviour/adaptive/lang/en_utf8/qbehaviour_adaptive.php  |    6 +
@@ -261,27 +282,6 @@ DONE question/type/truefalse/version.php                |    4 +-
  question/behaviour/opaque/renderer.php             |   65 +
  question/behaviour/opaque/simpletest/testopaquebehaviour.php      |  227 ++
 
- question/engine/bank.php                           |  221 ++
- question/engine/compatibility.php                  |  958 +++++++
- question/engine/datalib.php                        | 1069 ++++++++
- question/engine/lib.php                            | 2873 ++++++++++++++++++++
- question/engine/renderer.php                       |  362 +++
- question/engine/simpletest/helpers.php             |  629 +++++
- question/engine/simpletest/testdatalib.php         |  125 +
- question/engine/simpletest/testhtmlwriter.php      |   94 +
- question/engine/simpletest/testquestionattempt.php |  346 +++
- question/engine/simpletest/testquestionattemptiterator.php     |  108 +
- question/engine/simpletest/testquestionattemptstep.php  |  170 ++
- question/engine/simpletest/testquestionattemptstepiterator.php |  127 +
- question/engine/simpletest/testquestioncbm.php     |   40 +
- question/engine/simpletest/testquestionengine.php  |   94 +
- question/engine/simpletest/testquestionstate.php   |  154 ++
- question/engine/simpletest/testquestionusagebyactivity.php     |  153 ++
- question/engine/simpletest/testquestionutils.php   |  186 ++
- question/engine/states.php                         |  460 ++++
- question/engine/todo.txt                           |  235 ++
- question/engine/upgradefromoldqe/upgrade.php       | 1963 +++++++++++++
-
  question/format.php                                |   15 +-
  question/format/blackboard/format.php              |    2 +-
  question/format/gift/format.php                    |    6 +-
index 900529c..12ed53d 100644 (file)
@@ -197,7 +197,8 @@ abstract class question_definition {
      * @return qtype_renderer the renderer to use for outputting this question.
      */
     public function get_renderer() {
-        return renderer_factory::get_renderer('qtype_' . $this->qtype->name());
+        global $PAGE;
+        return $PAGE->get_renderer('qtype_' . $this->qtype->name());
     }
 
     /**
index cd2be0d..f7275ae 100644 (file)
@@ -761,7 +761,7 @@ class question_type {
      * script.js or script.php that exist in the plugin folder and ensures they
      * get included.
      */
-    protected function find_standard_scripts() {
+    public function find_standard_scripts() {
         global $PAGE;
 
         $plugindir = $this->plugin_dir();
index b2d5dd7..502aae0 100644 (file)
@@ -185,7 +185,8 @@ abstract class qtype_renderer extends plugin_renderer_base {
      * @return string HTML fragment.
      */
     public function head_code(question_attempt $qa) {
-        return implode("\n", $qa->get_question()->qtype->find_standard_scripts_and_css());
+        // TODO I think we can get rid of this, but what about Opaque?
+        $qa->get_question()->qtype->find_standard_scripts();
     }
 
     protected function feedback_class($fraction) {
index 023ed1e..17a99b4 100644 (file)
@@ -94,7 +94,7 @@ class qtype_truefalse_renderer extends qtype_renderer {
                 array('for' => $falseattributes['id']));
 
         $result = '';
-        $result .= html_writer::tag('div', $question->format_questiontext(),
+        $result .= html_writer::tag('div', $question->format_questiontext($qa),
                 array('class' => 'qtext'));
 
         $result .= html_writer::start_tag('div', array('class' => 'ablock'));