MDL-20636 Quiz editing now works, as does the random question type.
authorTim Hunt <T.J.Hunt@open.ac.uk>
Thu, 10 Feb 2011 20:44:47 +0000 (20:44 +0000)
committerTim Hunt <T.J.Hunt@open.ac.uk>
Thu, 10 Feb 2011 20:50:18 +0000 (20:50 +0000)
However, all this needs more testing.

Also, a bit of a purge of training whitespace and global .

40 files changed:
lib/adminlib.php
lib/questionlib.php
local/qedatabase/db/upgrade.php
mod/quiz/accessrules.php
mod/quiz/edit.js
mod/quiz/edit.php
mod/quiz/editlib.php
mod/quiz/locallib.php
question/addquestion.php
question/behaviour/behaviourbase.php
question/behaviour/immediatefeedback/simpletest/testwalkthrough.php
question/behaviour/interactive/simpletest/testwalkthrough.php
question/behaviour/rendererbase.php
question/category_class.php
question/editlib.php
question/engine/bank.php
question/engine/datalib.php
question/engine/lib.php
question/engine/upgradefromoldqe/upgrade.php
question/format/multianswer/format.php
question/format/xml/format.php
question/todo/diffstat.txt
question/type/calculated/datasetitems_form.php
question/type/edit_question_form.php
question/type/essay/renderer.php
question/type/gapselect/edit_form_base.php
question/type/match/db/upgrade.php
question/type/match/simpletest/testwalkthrough.php
question/type/multichoice/edit_multichoice_form.php
question/type/multichoice/renderer.php
question/type/numerical/lang/en/qtype_numerical.php
question/type/opaque/edit_opaque_form.php
question/type/opaque/simpletest/testlocallib.php
question/type/oumultiresponse/edit_oumultiresponse_form.php
question/type/questiontype.php
question/type/random/edit_random_form.php
question/type/random/lang/en/qtype_random.php
question/type/random/questiontype.php
question/type/random/simpletest/testquestiontype.php [new file with mode: 0644]
question/type/simpletest/testquestionbase.php

index f6edb2c..41337be 100644 (file)
@@ -4796,9 +4796,8 @@ class admin_page_manageqtypes extends admin_externalpage {
 
         $found = false;
         $textlib = textlib_get_instance();
-        require_once($CFG->libdir . '/questionlib.php');
-        global $QTYPES;
-        foreach ($QTYPES as $qtype) {
+        require_once($CFG->dirroot . '/question/engine/bank.php');
+        foreach (question_bank::get_all_qtypes() as $qtype) {
             if (strpos($textlib->strtolower($qtype->local_name()), $query) !== false) {
                 $found = true;
                 break;
index cf52035..7898cbe 100644 (file)
@@ -130,7 +130,7 @@ function question_type_menu() {
  * Sort an array of question type names according to the question type sort order stored in
  * config_plugins. Entries for which there is no xxx_sortorder defined will go
  * at the end, sorted according to textlib_get_instance()->asort($inarray).
- * @param $inarray an array $qtype => $QTYPES[$qtype]->local_name().
+ * @param $inarray an array $qtypename => $qtype->local_name().
  * @param $config get_config('question'), if you happen to have it around, to save one DB query.
  * @return array the sorted version of $inarray.
  */
@@ -431,7 +431,7 @@ function question_category_in_use($categoryid, $recursive = false) {
  * @param object $question  The question being deleted
  */
 function question_delete_question($questionid) {
-    global $QTYPES, $DB;
+    global $DB;
 
     $question = $DB->get_record_sql('
             SELECT q.*, qc.contextid
index 81c5baa..8fb8eba 100755 (executable)
@@ -1,7 +1,7 @@
 <?php
 
 function xmldb_local_qedatabase_upgrade($oldversion) {
-    global $CFG, $DB, $QTYPES;
+    global $CFG, $DB;
 
     $dbman = $DB->get_manager();
 
index eaec86e..219a482 100644 (file)
@@ -235,7 +235,7 @@ class quiz_access_manager {
      * @param boolean $unfinished whether the button is to continue an existing attempt,
      * or start a new one. This affects whether a javascript alert is shown.
      */
-    public function print_start_attempt_button($canpreview, $buttontext, $unfinished) { 
+    public function print_start_attempt_button($canpreview, $buttontext, $unfinished) {
         global $OUTPUT;
 
         $url = $this->_quizobj->start_attempt_url();
index a5d43f1..4a4d872 100644 (file)
@@ -1,4 +1,27 @@
-/** JavaScript for /mod/quiz/edit.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/>.
+
+/**
+ * JavaScript library for the quiz module editing interface.
+ *
+ * @package mod
+ * @subpackage quiz
+ * @copyright 2008 Olli Savolainen
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
 
 // Initialise everything on the quiz edit/order and paging page.
 var quiz_edit = {};
index 1f826c4..35c4ce8 100644 (file)
@@ -1,27 +1,20 @@
 <?php
 
-///////////////////////////////////////////////////////////////////////////
-//                                                                       //
-// NOTICE OF COPYRIGHT                                                   //
-//                                                                       //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
-//          http://moodle.org                                            //
-//                                                                       //
-// Copyright (C) 1999 onwards Martin Dougiamas and others                //
-//                                                                       //
-// This program 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 2 of the License, or     //
-// (at your option) any later version.                                   //
-//                                                                       //
-// This program 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:                          //
-//                                                                       //
-//          http://www.gnu.org/copyleft/gpl.html                         //
-//                                                                       //
-///////////////////////////////////////////////////////////////////////////
+// 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/>.
+
 
 /**
  * Page to edit quizzes
  * delete       Removes a question from the quiz
  * savechanges  Saves the order and grades for questions in the quiz
  *
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package quiz
- *//** */
+ * @package mod
+ * @subpackage quiz
+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
 
 require_once('../../config.php');
 require_once($CFG->dirroot . '/mod/quiz/editlib.php');
 require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
 require_once($CFG->dirroot . '/question/category_class.php');
 
+
 /**
  * Callback function called from question_list() function
  * (which is called from showbank())
@@ -74,7 +71,7 @@ function module_specific_buttons($cmid, $cmoptions) {
  * (which is called from showbank())
  */
 function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmoptions) {
-    global $QTYPES, $OUTPUT;
+    global $OUTPUT;
     $out = '';
     $catcontext = get_context_instance_by_id($category->contextid);
     if (has_capability('moodle/question:useall', $catcontext)) {
@@ -84,7 +81,8 @@ function module_specific_controls($totalnumber, $recurse, $category, $cmid, $cmo
             $disabled = '';
         }
         $randomusablequestions =
-                $QTYPES['random']->get_usable_questions_from_category($category->id, $recurse, '0');
+                question_bank::get_qtype('random')->get_available_questions_from_category(
+                        $category->id, $recurse);
         $maxrand = count($randomusablequestions);
         if ($maxrand > 0) {
             for ($i = 1; $i <= min(10, $maxrand); $i++) {
@@ -120,6 +118,7 @@ $quiz_qbanktool = optional_param('qbanktool', -1, PARAM_BOOL);
 
 list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
         question_edit_setup('editq', '/mod/quiz/edit.php', true);
+$quiz->questions = quiz_clean_layout($quiz->questions);
 
 $defaultcategoryobj = question_make_default_categories($contexts->all());
 $defaultcategory = $defaultcategoryobj->id . ',' . $defaultcategoryobj->contextid;
@@ -164,7 +163,7 @@ add_to_log($cm->course, 'quiz', 'editquestions',
 // You need mod/quiz:manage in addition to question capabilities to access this page.
 require_capability('mod/quiz:manage', $contexts->lowest());
 
-if (empty($quiz->grades)) {  // Construct an array to hold all the grades.
+if (empty($quiz->grades)) {
     $quiz->grades = quiz_get_all_question_grades($quiz);
 }
 
@@ -186,13 +185,15 @@ foreach ($params as $key => $value) {
 
 if (($up = optional_param('up', false, PARAM_INT)) && confirm_sesskey()) {
     $quiz->questions = quiz_move_question_up($quiz->questions, $up);
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
     redirect($thispageurl);
 }
 
 if (($down = optional_param('down', false, PARAM_INT)) && confirm_sesskey()) {
     $quiz->questions = quiz_move_question_down($quiz->questions, $down);
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
     redirect($thispageurl);
 }
 
@@ -200,15 +201,17 @@ if (optional_param('repaginate', false, PARAM_BOOL) && confirm_sesskey()) {
     // Re-paginate the quiz
     $questionsperpage = optional_param('questionsperpage', $quiz->questionsperpage, PARAM_INT);
     $quiz->questions = quiz_repaginate($quiz->questions, $questionsperpage );
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
+    redirect($thispageurl);
 }
 
 if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sesskey()) {
 /// Add a single question to the current quiz
     $addonpage = optional_param('addonpage', 0, PARAM_INT);
     quiz_add_quiz_question($addquestion, $quiz, $addonpage);
-    quiz_update_sumgrades($quiz);
     quiz_delete_previews($quiz);
+    quiz_update_sumgrades($quiz);
     $thispageurl->param('lastchanged', $addquestion);
     redirect($thispageurl);
 }
@@ -216,21 +219,17 @@ if (($addquestion = optional_param('addquestion', 0, PARAM_INT)) && confirm_sess
 if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
 /// Add selected questions to the current quiz
     $rawdata = (array) data_submitted();
-    foreach ($rawdata as $key => $value) {    // Parse input for question ids
+    foreach ($rawdata as $key => $value) { // Parse input for question ids
         if (preg_match('!^q([0-9]+)$!', $key, $matches)) {
             $key = $matches[1];
             quiz_add_quiz_question($key, $quiz);
         }
     }
-    quiz_update_sumgrades($quiz);
     quiz_delete_previews($quiz);
+    quiz_update_sumgrades($quiz);
     redirect($thispageurl);
 }
 
-$qcobject = new question_category_object($pagevars['cpage'], $thispageurl,
-        $contexts->having_one_edit_tab_cap('categories'), $defaultcategoryobj->id,
-        $defaultcategory, null, $contexts->having_cap('moodle/question:add'));
-
 if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
     // Add random questions to the quiz
     $recurse = optional_param('recurse', 0, PARAM_BOOL);
@@ -239,8 +238,8 @@ if ((optional_param('addrandom', false, PARAM_BOOL)) && confirm_sesskey()) {
     $randomcount = required_param('randomcount', PARAM_INT);
     quiz_add_random_questions($quiz, $addonpage, $categoryid, $randomcount, $recurse);
 
-    quiz_update_sumgrades($quiz);
     quiz_delete_previews($quiz);
+    quiz_update_sumgrades($quiz);
     redirect($thispageurl);
 }
 
@@ -248,21 +247,24 @@ if (optional_param('addnewpagesafterselected', null, PARAM_CLEAN) && !empty($sel
     foreach ($selectedquestionids as $questionid) {
         $quiz->questions = quiz_add_page_break_after($quiz->questions, $questionid);
     }
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
     redirect($thispageurl);
 }
 
 $addpage = optional_param('addpage', false, PARAM_INT);
 if ($addpage !== false && confirm_sesskey()) {
     $quiz->questions = quiz_add_page_break_at($quiz->questions, $addpage);
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
     redirect($thispageurl);
 }
 
 $deleteemptypage = optional_param('deleteemptypage', false, PARAM_INT);
 if (($deleteemptypage !== false) && confirm_sesskey()) {
     $quiz->questions = quiz_delete_empty_page($quiz->questions, $deleteemptypage);
-    quiz_save_new_layout($quiz);
+    $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+    quiz_delete_previews($quiz);
     redirect($thispageurl);
 }
 
@@ -278,12 +280,15 @@ if (optional_param('quizdeleteselected', false, PARAM_BOOL) && !empty($selectedq
     foreach ($selectedquestionids as $questionid) {
         quiz_remove_question($quiz, $questionid);
     }
-    quiz_update_sumgrades($quiz);
     quiz_delete_previews($quiz);
+    quiz_update_sumgrades($quiz);
     redirect($thispageurl);
 }
 
 if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
+    $deletepreviews = false;
+    $recomputesummarks = false;
+
     $oldquestions = explode(',', $quiz->questions); // the questions in the old order
     $questions = array(); // for questions in the new order
     $rawdata = (array) data_submitted();
@@ -298,9 +303,9 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
             /// Parse input for question -> grades
             $questionid = $matches[1];
             $quiz->grades[$questionid] = clean_param($value, PARAM_FLOAT);
-            quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz->id);
-            quiz_delete_previews($quiz);
-            quiz_update_sumgrades($quiz);
+            quiz_update_question_instance($quiz->grades[$questionid], $questionid, $quiz);
+            $deletepreviews = true;
+            $recomputesummarks = true;
 
         } else if (preg_match('!^o(pg)?([0-9]+)$!', $key, $matches)) {
             /// Parse input for ordering info
@@ -317,6 +322,7 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
             } else {
                 $questions[$value] = $questionid;
             }
+            $deletepreviews = true;
         }
     }
 
@@ -325,8 +331,8 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
         ksort($questions);
         $questions[] = 0;
         $quiz->questions = implode(',', $questions);
-        quiz_save_new_layout($quiz);
-        quiz_delete_previews($quiz);
+        $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+        $deletepreviews = true;
     }
 
     //get a list of questions to move, later to be added in the appropriate
@@ -350,8 +356,8 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
         $moveselectedpos = $pagebreakpositions[$moveselectedonpage - 1];
         array_splice($questions, $moveselectedpos, 0, $selectedquestionids);
         $quiz->questions = implode(',', $questions);
-        quiz_save_new_layout($quiz);
-        quiz_delete_previews($quiz);
+        $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
+        $deletepreviews = true;
     }
 
     // If rescaling is required save the new maximum
@@ -360,6 +366,15 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
         quiz_set_grade($maxgrade, $quiz);
     }
 
+    if ($deletepreviews) {
+        quiz_delete_previews($quiz);
+    }
+    if ($recomputesummarks) {
+        quiz_update_sumgrades($quiz);
+        quiz_update_all_attempt_sumgrades($quiz);
+        quiz_update_all_final_grades($quiz);
+        quiz_update_grades($quiz, 0, true);
+    }
     redirect($thispageurl);
 }
 
index f93d9c0..13c80b7 100644 (file)
@@ -1,37 +1,38 @@
 <?php
 
-///////////////////////////////////////////////////////////////////////////
-//                                                                       //
-// NOTICE OF COPYRIGHT                                                   //
-//                                                                       //
-// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
-//          http://moodle.org                                            //
-//                                                                       //
-// Copyright (C) 1999 onwards Martin Dougiamas and others                //
-//                                                                       //
-// This program 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 2 of the License, or     //
-// (at your option) any later version.                                   //
-//                                                                       //
-// This program 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:                          //
-//                                                                       //
-//          http://www.gnu.org/copyleft/gpl.html                         //
-//                                                                       //
-///////////////////////////////////////////////////////////////////////////
+// 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/>.
+
 
 /**
- * Functions used by edit.php to edit quizzes
+ * This contains functions that are called from within the quiz module only
+ * Functions that are also called by core Moodle are in {@link lib.php}
+ * This script also loads the code in {@link questionlib.php} which holds
+ * the module-indpendent code for handling questions and which in turn
+ * initialises all the questiontype classes.
  *
- * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
- * @package quiz
- *//** */
+ * @package mod
+ * @subpackage quiz
+ * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
 
 require_once($CFG->dirroot . '/mod/quiz/locallib.php');
 
+
 define('NUM_QS_TO_SHOW_IN_RANDOM', 3);
 
 /**
@@ -141,15 +142,16 @@ function quiz_add_quiz_question($id, $quiz, $page = 0) {
     $quiz->questions = implode(',', $questions);
     $DB->set_field('quiz', 'questions', $quiz->questions, array('id' => $quiz->id));
 
-    // update question grades
-    $quiz->grades[$id] = $DB->get_field('question', 'defaultgrade', array('id' => $id));
-    quiz_update_question_instance($quiz->grades[$id], $id, $quiz->instance);
-
-    return true;
+    // Add the new question instance.
+    $instance = new stdClass;
+    $instance->quiz = $quiz->id;
+    $instance->question = $id;
+    $instance->grade = $DB->get_field('question', 'defaultmark', array('id' => $id));
+    $DB->insert_record('quiz_question_instances', $instance);
 }
 
 function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, $includesubcategories) {
-    global $DB, $QTYPES;
+    global $DB;
 
     $category = $DB->get_record('question_categories', array('id' => $categoryid));
     if (!$category) {
@@ -163,7 +165,7 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, $inc
     // not used by any quiz.
     if ($existingquestions = $DB->get_records_sql(
             "SELECT q.id,q.qtype FROM {question} q
-            WHERE qtype = '" . RANDOM . "'
+            WHERE qtype = 'random'
                 AND category = ?
                 AND " . $DB->sql_compare_text('questiontext') . " = ?
                 AND NOT EXISTS (SELECT * FROM {quiz_question_instances} WHERE question = q.id)
@@ -181,14 +183,14 @@ function quiz_add_random_questions($quiz, $addonpage, $categoryid, $number, $inc
 
     // More random questions are needed, create them.
     $form->questiontext = array('text' => $includesubcategories, 'format' => 0);
-    $form->defaultgrade = 1;
+    $form->defaultmark = 1;
     $form->hidden = 1;
     for ($i = 0; $i < $number; $i += 1) {
         $form->category = $category->id . ',' . $category->contextid;
         $form->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
         $question = new stdClass;
-        $question->qtype = RANDOM;
-        $question = $QTYPES[RANDOM]->save_question($question, $form);
+        $question->qtype = 'random';
+        $question = question_bank::get_qtype('random')->save_question($question, $form);
         if (!isset($question->id)) {
             print_error('cannotinsertrandomquestion', 'quiz');
         }
@@ -248,23 +250,29 @@ function quiz_save_new_layout($quiz) {
  *
  * Saves changes to the question grades in the quiz_question_instances table.
  * It does not update 'sumgrades' in the quiz table.
- * @return boolean         Indicates success or failure.
+ *
  * @param integer grade    The maximal grade for the question
  * @param integer $questionid  The id of the question
  * @param integer $quizid  The id of the quiz to update / add the instances for.
  */
-function quiz_update_question_instance($grade, $questionid, $quizid) {
-    global $DB;
-    if ($instance = $DB->get_record('quiz_question_instances', array('quiz' => $quizid, 'question' => $questionid))) {
-        $instance->grade = $grade;
-        return $DB->update_record('quiz_question_instances', $instance);
-    } else {
-        unset($instance);
-        $instance->quiz = $quizid;
-        $instance->question = $questionid;
-        $instance->grade = $grade;
-        return $DB->insert_record('quiz_question_instances', $instance);
+function quiz_update_question_instance($grade, $questionid, $quiz) {
+    $instance = $DB->get_record('quiz_question_instances', array('quiz' => $quizid,
+            'question' => $questionid));
+    $slot = quiz_get_slot_for_question($quiz, $questionid);
+
+    if (!$instance || !$slot) {
+        throw new coding_exception('Attempt to change the grade of a quesion not in the quiz.');
     }
+
+    if (abs($grade - $instance->grade) < 1e-7) {
+        // Grade has not changed. Nothing to do.
+        return;
+    }
+
+    $instance->grade = $grade;
+    $DB->update_record('quiz_question_instances', $instance);
+    question_engine::set_max_mark_in_attempts(new quibaid_for_quiz($quiz->id),
+            $slot, $grade);
 }
 
 // Private function used by the following two.
@@ -330,7 +338,7 @@ function quiz_move_question_down($layout, $questionid) {
  */
 function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
         $quiz_qbanktool, $hasattempts, $defaultcategoryobj) {
-    global $USER, $CFG, $QTYPES, $DB, $OUTPUT;
+    global $USER, $CFG, $DB, $OUTPUT;
     $strorder = get_string('order');
     $strquestionname = get_string('questionname', 'quiz');
     $strgrade = get_string('grade');
@@ -351,11 +359,12 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
 
     if ($quiz->questions) {
         list($usql, $params) = $DB->get_in_or_equal(explode(',', $quiz->questions));
-        $questions = $DB->get_records_sql("SELECT q.*,c.contextid
-                              FROM {question} q,
-                                   {question_categories} c
-                             WHERE q.id $usql
-                               AND q.category = c.id", $params);
+        $params[] = $quiz->id;
+        $questions = $DB->get_records_sql("SELECT q.*, qc.contextid, qqi.grade as maxmark
+                              FROM {question} q
+                              JOIN {question_categories} qc ON qc.id = q.category
+                              JOIN {quiz_question_instances} qqi ON qqi.question = q.id
+                             WHERE q.id $usql AND qqi.quiz = ?", $params);
     } else {
         $questions = array();
     }
@@ -429,20 +438,15 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
     $qno = 1;
     //the current question (includes questions and descriptions)
     $questioncount = 0;
-    //the ordinal of current element in the layout
-    //(includes page breaks, questions and descriptions)
-    $count = 0;
     //the current page number in iteration
     $pagecount = 0;
 
-    $sumgrade = 0;
-
     $pageopen = false;
 
     $returnurl = str_replace($CFG->wwwroot, '', $pageurl->out(false));
     $questiontotalcount = count($order);
 
-    foreach ($order as $i => $qnum) {
+    foreach ($order as $count => $qnum) {
 
         $reordercheckbox = '';
         $reordercheckboxlabel = '';
@@ -451,10 +455,22 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
         if ($qnum && empty($questions[$qnum])) {
             continue;
         }
+
         // If the questiontype is missing change the question type
-        if ($qnum && !array_key_exists($questions[$qnum]->qtype, $QTYPES)) {
+        if ($qnum && !array_key_exists($qnum, $questions)) {
+            $fakequestion = new stdClass();
+            $fakequestion->id = 0;
+            $fakequestion->qtype = 'missingtype';
+            $fakequestion->name = get_string('deletedquestion', 'qtype_missingtype');
+            $fakequestion->questiontext = '<p>' . get_string('deletedquestion', 'qtype_missing') . '</p>';
+            $fakequestion->length = 0;
+            $questions[$qnum] = $fakequestion;
+            $quiz->grades[$qnum] = 0;
+
+        } else if ($qnum and question_bank::qtype_exists($questions[$qnum]->qtype)) {
             $questions[$qnum]->qtype = 'missingtype';
         }
+
         if ($qnum != 0 || ($qnum == 0 && !$pageopen)) {
             //this is either a question or a page break after another
             //        (no page is currently open)
@@ -466,7 +482,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
                         '</span><div class="pagecontent">';
                 $pageopen = true;
             }
-            if ($qnum == 0  && $i < $questiontotalcount) {
+            if ($qnum == 0  && $count < $questiontotalcount) {
                 // This is the second successive page break. Tell the user the page is empty.
                 echo '<div class="pagestatus">';
                 print_string('noquestionsonpage', 'quiz');
@@ -474,7 +490,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
                 if ($allowdelete) {
                     echo '<div class="quizpagedelete">';
                     echo '<a title="' . get_string('removeemptypage', 'quiz') . '" href="' .
-                            $pageurl->out(true, array('deleteemptypage' => $i - 1, 'sesskey'=>sesskey())) .
+                            $pageurl->out(true, array('deleteemptypage' => $count - 1, 'sesskey'=>sesskey())) .
                             '"><img src="' . $OUTPUT->pix_url('t/delete') . '" ' .
                             'class="iconsmall" alt="' . $strremove . '" /></a>';
                     echo '</div>';
@@ -551,7 +567,7 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
                             " alt=\"$strmovedown\" /></a>";
                 }
             }
-            if ($allowdelete && question_has_capability_on($question, 'use', $question->category)) {
+            if ($allowdelete && (empty($question->id) || question_has_capability_on($question, 'use', $question->category))) {
             // remove from quiz, not question delete.
                 if (!$hasattempts) {
                     echo "<a title=\"$strremove\" href=\"" .
@@ -625,19 +641,15 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
 </div>
 
     <?php
-            /* Display question end */
-                $count++;
-                $sumgrade += $quiz->grades[$qnum];
-
             }
         }
         //a page break: end the existing page.
         if ($qnum == 0) {
             if ($pageopen) {
-                if (!$reordertool && !($quiz->shufflequestions && $i < $questiontotalcount - 1)) {
+                if (!$reordertool && !($quiz->shufflequestions && $count < $questiontotalcount - 1)) {
                     quiz_print_pagecontrols($quiz, $pageurl, $pagecount,
                             $hasattempts, $defaultcategoryobj);
-                } else if ($i < $questiontotalcount - 1) {
+                } else if ($count < $questiontotalcount - 1) {
                     //do not include the last page break for reordering
                     //to avoid creating a new extra page in the end
                     echo '<input type="hidden" name="opg' . $pagecount . '" size="2" value="' .
@@ -661,8 +673,6 @@ function quiz_print_question_list($quiz, $pageurl, $allowdelete, $reordertool,
         echo $reordercontrolsbottom;
         echo '</div></form>';
     }
-
-    return $sumgrade;
 }
 
 /**
@@ -732,13 +742,11 @@ function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts, $defaultc
  * @param object $quiz The quiz in the context of which the question is being displayed
  */
 function quiz_print_singlequestion($question, $returnurl, $quiz) {
-    global $QTYPES;
     echo '<div class="singlequestion">';
     echo quiz_question_edit_button($quiz->cmid, $question, $returnurl, quiz_question_tostring($question) . ' ');
     echo '<span class="questiontype">';
-    $namestr = $QTYPES[$question->qtype]->local_name();
     print_question_icon($question);
-    echo " $namestr</span>";
+    echo ' ' . question_bank::get_qtype_name($question->qtype) . '</span>';
     echo '<span class="questionpreview">' . quiz_question_preview_button($quiz, $question, true) . '</span>';
     echo "</div>\n";
 }
@@ -752,7 +760,7 @@ function quiz_print_singlequestion($question, $returnurl, $quiz) {
  * @param boolean $quiz_qbanktool Indicate to this function if the question bank window open
  */
 function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktool) {
-    global $DB, $QTYPES, $OUTPUT;
+    global $DB, $OUTPUT;
     echo '<div class="quiz_randomquestion">';
 
     if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
@@ -778,7 +786,7 @@ function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktoo
     echo '<span class="questionpreview">' . quiz_question_preview_button($quiz, $question, true) . '</span>';
     echo '</div>';
 
-    $questionids = $QTYPES['random']->get_usable_questions_from_category(
+    $questionids = question_bank::get_qtype('random')->get_usable_questions_from_category(
             $category->id, $question->questiontext == '1', '0');
     $questioncount = count($questionids);
 
@@ -829,7 +837,6 @@ function quiz_print_randomquestion(&$question, &$pageurl, &$quiz, $quiz_qbanktoo
     echo '<div class="randomquestioncategorycount">';
     echo '</div>';
     echo '</div>';
-
 }
 
 /**
@@ -860,14 +867,14 @@ function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) {
  * @param object $quiz The quiz in the context of which the question is being displayed
  */
 function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
-    global $DB, $QTYPES, $OUTPUT;
+    global $DB, $OUTPUT;
 
     // Load the category, and the number of available questions in it.
     if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
         echo $OUTPUT->notification('Random question category not found!');
         return;
     }
-    $questioncount = count($QTYPES['random']->get_usable_questions_from_category(
+    $questioncount = count(question_bank::get_qtype('random')->get_usable_questions_from_category(
             $category->id, $question->questiontext == '1', '0'));
 
     $reordercheckboxlabel = '<label for="s' . $question->id . '">';
@@ -898,7 +905,6 @@ function quiz_print_randomquestion_reordertool(&$question, &$pageurl, &$quiz) {
     echo '<div class="randomquestioncategorycount">';
     echo '</div>';
     echo '</div>';
-
 }
 
 /**
@@ -916,7 +922,6 @@ function print_random_option_icon($question) {
     }
     echo '<img src="' . $OUTPUT->pix_url('i/' . $icon) . '" alt="' .
             $tooltip . '" title="' . $tooltip . '" class="uihint" />';
-
 }
 
 /**
@@ -928,39 +933,38 @@ function print_random_option_icon($question) {
  *       If false, show only question name.
  * @param boolean $return If true (default), return the output. If false, print it.
  */
-
 function quiz_question_tostring(&$question, $showicon = false, $showquestiontext = true, $return = true) {
-        global $COURSE;
-        $result = '';
-        $result .= '<span class="questionname">';
-        if ($showicon) {
-            $result .= print_question_icon($question, true);
-            echo " ";
-        }
-        $result .= shorten_text(format_string($question->name), 200) . '</span>';
-        if ($showquestiontext) {
-            $formatoptions = new stdClass;
-            $formatoptions->noclean = true;
-            $formatoptions->para = false;
-            $questiontext = strip_tags(format_text($question->questiontext,
-                    $question->questiontextformat,
-                    $formatoptions, $COURSE->id));
-            $questiontext = shorten_text($questiontext, 200);
-            $result .= '<span class="questiontext">';
-            if (!empty($questiontext)) {
-                $result .= $questiontext;
-            } else {
-                $result .= '<span class="error">';
-                $result .= get_string('questiontextisempty', 'quiz');
-                $result .= '</span>';
-            }
-            $result .= '</span>';
-        }
-        if ($return) {
-            return $result;
+    global $COURSE;
+    $result = '';
+    $result .= '<span class="questionname">';
+    if ($showicon) {
+        $result .= print_question_icon($question, true);
+        echo ' ';
+    }
+    $result .= shorten_text(format_string($question->name), 200) . '</span>';
+    if ($showquestiontext) {
+        $formatoptions = new stdClass;
+        $formatoptions->noclean = true;
+        $formatoptions->para = false;
+        $questiontext = strip_tags(format_text($question->questiontext,
+                $question->questiontextformat,
+                $formatoptions, $COURSE->id));
+        $questiontext = shorten_text($questiontext, 200);
+        $result .= '<span class="questiontext">';
+        if (!empty($questiontext)) {
+            $result .= $questiontext;
         } else {
-            echo $result;
+            $result .= '<span class="error">';
+            $result .= get_string('questiontextisempty', 'quiz');
+            $result .= '</span>';
         }
+        $result .= '</span>';
+    }
+    if ($return) {
+        return $result;
+    } else {
+        echo $result;
+    }
 }
 
 /**
@@ -1202,5 +1206,3 @@ function quiz_print_status_bar($quiz) {
     </div>
     <?php
 }
-
-?>
index 20093ec..33ef60b 100644 (file)
@@ -917,11 +917,11 @@ function quiz_question_edit_button($cmid, $question, $returnurl, $contentafteric
 
     // What sort of icon should we show?
     $action = '';
-    if (question_has_capability_on($question, 'edit', $question->category) ||
-            question_has_capability_on($question, 'move', $question->category)) {
+    if (!empty($question->id) && (question_has_capability_on($question, 'edit', $question->category) ||
+            question_has_capability_on($question, 'move', $question->category))) {
         $action = $stredit;
         $icon = '/t/edit';
-    } else if (question_has_capability_on($question, 'view', $question->category)) {
+    } else if (!empty($question->id) && question_has_capability_on($question, 'view', $question->category)) {
         $action = $strview;
         $icon = '/i/info';
     }
@@ -965,6 +965,7 @@ function quiz_question_preview_button($quiz, $question, $label = false) {
     }
 
     // Build the icon.
+    $strpreviewquestion = get_string('previewquestion', 'quiz');
     $image = $OUTPUT->pix_icon('t/preview', $strpreviewquestion);
 
     parse_str(QUESTION_PREVIEW_POPUP_OPTIONS, $options);
index 01325dd..7287150 100644 (file)
@@ -87,12 +87,12 @@ if ($cm !== null) {
         $PAGE->navbar->add(get_string('editinga', 'moodle', get_string('modulename', $cm->modname)),$returnurl);
     }
     $PAGE->navbar->add($chooseqtype);
-    $PAGE->set_title($chooseqtype);    
+    $PAGE->set_title($chooseqtype);
     echo $OUTPUT->header();
 } else {
     $PAGE->navbar->add(get_string('questionbank', 'question'),$returnurl);
     $PAGE->navbar->add($chooseqtype);
-    $PAGE->set_title($chooseqtype);    
+    $PAGE->set_title($chooseqtype);
     echo $OUTPUT->header();
 }
 
index 46add1d..9605af3 100644 (file)
@@ -299,7 +299,7 @@ abstract class question_behaviour {
 
     /**
      * @return array subpartid => object with fields
-     *      ->responseclassid the 
+     *      ->responseclassid matches one of the values returned from quetion_type::get_possible_responses.
      *      ->response the actual response the student gave to this part, as a string.
      *      ->fraction the credit awarded for this subpart, may be null.
      *      returns an empty array if no analysis is possible.
index 6811eaf..4523c1c 100644 (file)
@@ -163,7 +163,7 @@ class qbehaviour_immediatefeedback_walkthrough_test extends qbehaviour_walkthrou
                 $this->get_does_not_contain_feedback_expectation(),
                 $this->get_contains_validation_error_expectation());
         $this->assertNull($this->quba->get_response_summary($this->slot));
-    
+
         // Finish the attempt.
         $this->quba->finish_all_questions();
 
index 570ad9a..1287dc1 100644 (file)
@@ -464,7 +464,7 @@ class qbehaviour_interactive_walkthrough_test extends qbehaviour_walkthrough_tes
         $this->check_current_state(question_state::$gradedpartial);
         // TODO I don't think 1 is the right fraction here. However, it is what
         // you get attempting a question like this without regrading being involved,
-        // and I am currently interested in testing regrading here. 
+        // and I am currently interested in testing regrading here.
         $this->check_current_mark(1);
     }
 }
index b4c80ce..a4b9c22 100644 (file)
@@ -123,7 +123,6 @@ abstract class qbehaviour_renderer extends plugin_renderer_base {
                     html_writer::tag('div', $error . get_string('xoutofmax', 'question', $a) .
                         $markrange, array('class' => 'felement ftext' . $errorclass)
                     ), array('class' => 'fitem'));
-            
         }
 
         return html_writer::tag('fieldset', html_writer::tag('div', $comment . $mark,
index 195e29c..c007038 100644 (file)
@@ -374,7 +374,7 @@ class question_category_object {
      * Updates an existing category with given params
      */
     public function update_category($updateid, $newparent, $newname, $newinfo) {
-        global $CFG, $QTYPES, $DB;
+        global $CFG, $DB;
         if (empty($newname)) {
             print_error('categorynamecantbeblank', 'quiz');
         }
@@ -413,10 +413,11 @@ class question_category_object {
         if ($oldcat->name != $cat->name) {
             $where = "qtype = 'random' AND category = ? AND " . $DB->sql_compare_text('questiontext') . " = ?";
 
-            $randomqname = $QTYPES[RANDOM]->question_name($cat, false);
+            $randomqtype = question_bank::get_qtype('random');
+            $randomqname = $randomqtype->question_name($cat, false);
             $DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '0'));
 
-            $randomqname = $QTYPES[RANDOM]->question_name($cat, true);
+            $randomqname = $randomqtype->question_name($cat, true);
             $DB->set_field_select('question', 'name', $randomqname, $where, array($cat->id, '1'));
         }
 
index d03a78c..30ab769 100644 (file)
@@ -58,8 +58,7 @@ function get_module_from_cmid($cmid) {
 * @author added by Howard Miller June 2004
 */
 function get_questions_category( $category, $noparent=false, $recurse=true, $export=true ) {
-
-    global $QTYPES, $DB;
+    global $DB;
 
     // questions will be added to an array
     $qresults = array();
@@ -84,9 +83,8 @@ function get_questions_category( $category, $noparent=false, $recurse=true, $exp
 
         // iterate through questions, getting stuff we need
         foreach($questions as $question) {
-            $questiontype = $QTYPES[$question->qtype];
             $question->export_process = $export;
-            $questiontype->get_question_options($question);
+            question_bank::get_qtype($question->qtype)->get_question_options($question);
             $qresults[] = $question;
         }
     }
@@ -1679,7 +1677,7 @@ function require_login_in_context($contextorid = null){
  * the qtype radio buttons.
  */
 function print_choose_qtype_to_add_form($hiddenparams) {
-    global $CFG, $QTYPES, $PAGE, $OUTPUT;
+    global $CFG, $PAGE, $OUTPUT;
     $PAGE->requires->js('/question/qbank.js');
     echo '<div id="chooseqtypehead" class="hd">' . "\n";
     echo $OUTPUT->heading(get_string('chooseqtypetoadd', 'question'), 3);
@@ -1693,19 +1691,18 @@ function print_choose_qtype_to_add_form($hiddenparams) {
     echo '<div class="qtypes">' . "\n";
     echo '<div class="instruction">' . get_string('selectaqtypefordescription', 'question') . "</div>\n";
     echo '<div class="realqtypes">' . "\n";
-    $types = question_type_menu();
     $fakeqtypes = array();
-    foreach ($types as $qtype => $localizedname) {
-        if ($QTYPES[$qtype]->is_real_question_type()) {
-            print_qtype_to_add_option($qtype, $localizedname);
+    foreach (question_bank::get_creatable_qtypes() as $qtype) {
+        if (question_bank::get_qtype($qtype)->is_real_question_type()) {
+            print_qtype_to_add_option($qtype);
         } else {
-            $fakeqtypes[$qtype] = $localizedname;
+            $fakeqtypes[] = $qtype;
         }
     }
     echo "</div>\n";
     echo '<div class="fakeqtypes">' . "\n";
     foreach ($fakeqtypes as $qtype => $localizedname) {
-        print_qtype_to_add_option($qtype, $localizedname);
+        print_qtype_to_add_option($qtype);
     }
     echo "</div>\n";
     echo "</div>\n";
@@ -1720,18 +1717,17 @@ function print_choose_qtype_to_add_form($hiddenparams) {
 /**
  * Private function used by the preceding one.
  * @param $qtype the question type.
- * @param $localizedname the localized name of this question type.
  */
-function print_qtype_to_add_option($qtype, $localizedname) {
-    global $QTYPES;
+function print_qtype_to_add_option($qtype) {
     echo '<div class="qtypeoption">' . "\n";
-    echo '<label for="qtype_' . $qtype . '">';
-    echo '<input type="radio" name="qtype" id="qtype_' . $qtype . '" value="' . $qtype . '" />';
+    echo '<label for="qtype_' . $qtype->name() . '">';
+    echo '<input type="radio" name="qtype" id="qtype_' . $qtype->name() . '" value="' . $qtype->name() . '" />';
     echo '<span class="qtypename">';
     $fakequestion = new stdClass;
-    $fakequestion->qtype = $qtype;
+    $fakequestion->qtype = $qtype->name();
     print_question_icon($fakequestion);
-    echo $localizedname . '</span><span class="qtypesummary">' . get_string($qtype . 'summary', 'qtype_' . $qtype);
+    echo $qtype->menu_name() . '</span><span class="qtypesummary">' .
+            get_string($qtype->name() . 'summary', 'qtype_' . $qtype->name());
     echo "</span></label>\n";
     echo "</div>\n";
 }
index 1c1cb66..012ad3f 100644 (file)
@@ -51,6 +51,8 @@ abstract class question_bank {
     private static $testmode = false;
     private static $testdata = array();
 
+    private static $questionconfig = null;
+
     /**
      * Get the question type class for a particular question type.
      * @param string $qtypename the question type name. For example 'multichoice' or 'shortanswer'.
@@ -80,12 +82,34 @@ abstract class question_bank {
         return self::$questiontypes[$qtypename];
     }
 
+    /**
+     * Load the question configuration data from config_plugins.
+     * @return object get_config('question') with caching.
+     */
+    protected static function get_config() {
+        if (is_null(self::$questionconfig)) {
+            $questionconfig = get_config('question');
+        }
+        return $questionconfig;
+    }
+
     /**
      * @param string $qtypename the internal name of a question type. For example multichoice.
      * @return boolean whether users are allowed to create questions of this type.
      */
     public static function qtype_enabled($qtypename) {
-        return true; // TODO
+        $config = self::get_config();
+        $enabledvar = $qtypename . '_disabled';
+        return self::qtype_exists($qtypename) && empty($config->$enabledvar) &&
+                self::get_qtype($qtypename)->menu_name() != '';
+    }
+
+    /**
+     * @param string $qtypename the internal name of a question type. For example multichoice.
+     * @return boolean whether this question type exists.
+     */
+    public static function qtype_exists($qtypename) {
+        return array_key_exists($qtypename, get_plugin_list('qtype'));
     }
 
     /**
@@ -93,7 +117,7 @@ abstract class question_bank {
      * @return string the human_readable name of this question type, from the language pack.
      */
     public static function get_qtype_name($qtypename) {
-        return self::get_qtype($qtypename)->menu_name();
+        return self::get_qtype($qtypename)->local_name();
     }
 
     /**
@@ -111,6 +135,42 @@ abstract class question_bank {
         return $qtypes;
     }
 
+    /**
+     * @return array all the question types that users are allowed to create,
+     *      sorted into the preferred order set on the admin screen.
+     */
+    public static function get_creatable_qtypes() {
+        $config = self::get_config();
+        $allqtypes = self::get_all_qtypes();
+
+        $sortorder = array();
+        $otherqtypes = array();
+        foreach ($allqtypes as $name => $qtype) {
+            if (!self::qtype_enabled($name)) {
+                unset($allqtypes[$name]);
+                continue;
+            }
+            $sortvar = $name . '_sortorder';
+            if (isset($config->$sortvar)) {
+                $sortorder[$config->$sortvar] = $name;
+            } else {
+                $otherqtypes[$name] = $qtype->local_name();
+            }
+        }
+
+        ksort($sortorder);
+        textlib_get_instance()->asort($otherqtypes);
+
+        $creatableqtypes = array();
+        foreach ($sortorder as $name) {
+            $creatableqtypes[$name] = $allqtypes[$name];
+        }
+        foreach ($otherqtypes as $name => $notused) {
+            $creatableqtypes[$name] = $allqtypes[$name];
+        }
+        return $qtypes;
+    }
+
     /**
      * Load the question definition class(es) belonging to a question type. That is,
      * include_once('/question/type/' . $qtypename . '/question.php'), with a bit
@@ -222,6 +282,8 @@ class question_finder {
      * @return array questionid => questionid.
      */
     public function get_questions_from_categories($categoryids, $extraconditions) {
+        global $DB;
+
         if (is_array($categoryids)) {
             $categoryids = implode(',', $categoryids);
         }
@@ -234,10 +296,7 @@ class question_finder {
                 "category IN ($categoryids)
                  AND parent = 0
                  AND hidden = 0
-                 $extraconditions", '', 'id,id AS id2');
-        if (!$questionids) {
-            $questionids = array();
-        }
+                 $extraconditions", array(), '', 'id,id AS id2');
         return $questionids;
     }
 }
index 5911500..dd67067 100644 (file)
@@ -349,7 +349,7 @@ GROUP BY
     q.id,
     summarystate
 
-ORDER BY 
+ORDER BY
     qa.slot,
     qa.questionid,
     q.name,
@@ -695,7 +695,7 @@ ORDER BY
      * @param boolean $newstate the new state of the flag. true = flagged.
      */
     public function update_question_attempt_flag($qubaid, $questionid, $qaid, $slot, $newstate) {
-        if (!$this->db->record_exists('question_attempts', array('id' => $qaid, 
+        if (!$this->db->record_exists('question_attempts', array('id' => $qaid,
                 'questionusageid' => $qubaid, 'questionid' => $questionid, 'slot' => $slot))) {
             throw new Exception('invalid ids');
         }
index 2293f43..ddc3434 100644 (file)
@@ -538,7 +538,7 @@ abstract class question_flags {
      */
     public static function update_flag($qubaid, $questionid, $qaid, $slot, $checksum, $newstate) {
         // Check the checksum - it is very hard to know who a question session belongs
-        // to, so we require that checksum parameter is matches an md5 hash of the 
+        // to, so we require that checksum parameter is matches an md5 hash of the
         // three ids and the users username. Since we are only updating a flag, that
         // probably makes it sufficiently difficult for malicious users to toggle
         // other users flags.
@@ -1083,7 +1083,6 @@ class question_usage_by_activity {
                 $qa->set_flagged($flagged);
             }
         }
-        
     }
 
     /**
@@ -2174,7 +2173,7 @@ class question_attempt {
 
     /**
      * @return array subpartid => object with fields
-     *      ->responseclassid the 
+     *      ->responseclassid matches one of the values returned from quetion_type::get_possible_responses.
      *      ->response the actual response the student gave to this part, as a string.
      *      ->fraction the credit awarded for this subpart, may be null.
      *      returns an empty array if no analysis is possible.
index a87a16e..c9290fa 100644 (file)
@@ -224,7 +224,7 @@ class question_engine_attempt_upgrader {
         $this->logger->set_current_attempt_id(null);
 
         if (empty($qas)) {
-            $this->logger->log_assumption("All the question attempts for 
+            $this->logger->log_assumption("All the question attempts for
                     attempt {$attempt->id} at quiz {$attempt->quiz} were missing.
                     Deleting this attempt", $attempt->id);
             // Somehow, all the question attempt data for this quiz attempt
@@ -239,7 +239,7 @@ class question_engine_attempt_upgrader {
                 continue;
             }
             if (!array_key_exists($questionid, $qas)) {
-                $this->logger->log_assumption("Supplying minimal open state for 
+                $this->logger->log_assumption("Supplying minimal open state for
                         question {$questionid} in attempt {$attempt->id} at quiz
                         {$attempt->quiz}, since the session was missing.", $attempt->id);
                 try {
@@ -1877,7 +1877,7 @@ class qtype_opaque_updater extends qtype_updater {
         // We store the reponses by turning the associative array $state->responses
         // into a string as follows. For example, array('f2' => 'No, never - ever', 'f1' => '10')
         // becomes 'f1-10,f2-No\, never - ever'. That is, comma separated pairs, sorted by key,
-        // key and value linked with a '-', commas in vales escaped with '\'. 
+        // key and value linked with a '-', commas in vales escaped with '\'.
 
         // Deal with special case: no responses at all.
         if (empty($answer)) {
index 286854f..48766d8 100644 (file)
@@ -47,7 +47,7 @@ class qformat_multianswer extends qformat_default {
         $questiontext = array();
         $questiontext['text'] = implode('', $lines);
         $questiontext['format'] = 0 ;
-        $questiontext['itemid'] = ''; 
+        $questiontext['itemid'] = '';
         $question = qtype_multianswer_extract_question($questiontext);
         $question->questiontext = $question->questiontext['text'] ;
         $question->questiontextformat = 0 ;
index 1555b86..465a032 100644 (file)
@@ -419,7 +419,7 @@ class qformat_xml extends qformat_default {
         $questiontext = array();
         $questiontext['text'] = $this->import_text($questions['#']['questiontext'][0]['#']['text']);
         $questiontext['format'] = '1';
-        $questiontext['itemid'] = ''; 
+        $questiontext['itemid'] = '';
         $qo = qtype_multianswer_extract_question($questiontext);
 
         // 'header' parts particular to multianswer
index 0bb48ae..4b70098 100644 (file)
@@ -75,9 +75,9 @@ 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 +-
+DONE mod/quiz/edit.js                                   |  131 +
+DONE mod/quiz/edit.php                                  |  118 +-
+DONE mod/quiz/editlib.php                               |   79 +-
 DONE mod/quiz/index.php                                 |    6 +-
 DONE mod/quiz/jstimer.php                               |   58 -
 DONE mod/quiz/lib.php                                   |  454 ++--
@@ -415,10 +415,10 @@ DONE question/type/oumultiresponse/simpletest/testwalkthrough.php |  501 ++++
 DONE question/type/oumultiresponse/styles.css           |   14 +
 DONE question/type/oumultiresponse/version.php          |   29 +
 
- question/type/random/edit_random_form.php          |    7 +-
- question/type/random/lang/en_utf8/qtype_random.php |    9 +
- question/type/random/questiontype.php              |  430 +---
- question/type/random/simpletest/testquestiontype.php    |   61 +
+DONE question/type/random/edit_random_form.php          |    7 +-
+DONE question/type/random/lang/en_utf8/qtype_random.php |    9 +
+DONE question/type/random/questiontype.php              |  430 +---
+DONE question/type/random/simpletest/testquestiontype.php    |   61 +
 
  question/type/randomsamatch/db/upgrade.php         |    2 -
  question/type/randomsamatch/edit_randomsamatch_form.php |    3 +-
index e3253d5..201cc28 100644 (file)
@@ -87,7 +87,7 @@ class question_dataset_dependent_items_form extends moodleform {
         $data = array();
         $j = (($this->noofitems) * count($this->datasetdefs))+1;
         foreach ($this->datasetdefs as $defkey => $datasetdef){
-            if($datasetdef->category |= 0 ) { 
+            if($datasetdef->category |= 0 ) {
                 $name = get_string('sharedwildcardname', 'qtype_calculated',$datasetdef->name) ;
             }else {
                 $name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
index e29fd1b..c029d19 100644 (file)
@@ -90,7 +90,7 @@ abstract class question_edit_form extends moodleform {
         $qtype = $this->qtype();
         $langfile = "qtype_$qtype";
 
-        $mform = $this->_form; 
+        $mform = $this->_form;
 
         // Standard fields at the start of the form.
         $mform->addElement('header', 'generalheader', get_string("general", 'form'));
@@ -376,8 +376,7 @@ abstract class question_edit_form extends moodleform {
     }
 
     public function set_data($question) {
-        global $QTYPES;
-        $QTYPES[$question->qtype]->set_default_options($question);
+        question_bank::get_qtype($question->qtype)->set_default_options($question);
 
         // prepare question text
         $draftid = file_get_submitted_draft_itemid('questiontext');
@@ -416,7 +415,7 @@ abstract class question_edit_form extends moodleform {
         }
 
         // Set any options.
-        $extra_question_fields = $QTYPES[$question->qtype]->extra_question_fields();
+        $extra_question_fields = question_bank::get_qtype($question->qtype)->extra_question_fields();
         if (is_array($extra_question_fields) && !empty($question->options)) {
             array_shift($extra_question_fields);
             foreach ($extra_question_fields as $field) {
index 58702d9..02f430f 100644 (file)
@@ -57,7 +57,7 @@ class qtype_essay_renderer extends qtype_renderer {
                 array('class' => 'qtext'));
 
         $result .= html_writer::start_tag('div', array('class' => 'ablock clearfix'));
-        $result .= html_writer::tag('div', get_string('answer', 'question'), 
+        $result .= html_writer::tag('div', get_string('answer', 'question'),
                 array('class' => 'prompt'));
         $result .= html_writer::tag('div', $answer, array('class' => 'answer'));
         $result .= html_writer::end_tag('div');
index 26c97a0..a9ea332 100644 (file)
@@ -27,7 +27,7 @@ class qtype_gapselect_edit_form_base extends question_edit_form {
     private $htmltclosetags = '~<\s*/\s*\w\s*.*?>|<\s*br\s*>~';
 
     /** @var string regex to select text like [[cat]] (including the square brackets). */
-    private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';  
+    private $squareBracketsRegex = '/\[\[[^]]*?\]\]/';
 
     private function get_html_tags($text) {
         $textarray = array();
index 625efcc..4ffa61c 100644 (file)
@@ -21,7 +21,7 @@
 // before any action that may take longer time to finish.
 
 function xmldb_qtype_match_upgrade($oldversion) {
-    global $CFG, $DB, $QTYPES;
+    global $CFG, $DB;
 
     $dbman = $DB->get_manager();
 
index eba0b2c..462a907 100644 (file)
@@ -114,7 +114,6 @@ class qtype_match_walkthrough_test extends qbehaviour_walkthrough_test_base {
         // Save a partial response.
         $this->process_submission(array('sub0' => $orderforchoice[1],
                 'sub1' => $orderforchoice[2], 'sub2' => '0', 'sub3' => '0'));
-        
 
         // Verify.
         $this->check_current_state(question_state::$todo);
index 59c4555..4e53efa 100644 (file)
@@ -39,8 +39,6 @@ class qtype_multichoice_edit_form extends question_edit_form {
      * @param object $mform the form being built.
      */
     protected function definition_inner($mform) {
-        global $QTYPES;
-
         $menu = array(get_string('answersingleno', 'qtype_multichoice'), get_string('answersingleyes', 'qtype_multichoice'));
         $mform->addElement('select', 'single', get_string('answerhowmany', 'qtype_multichoice'), $menu);
         $mform->setDefault('single', 1);
index aef9399..667cb24 100644 (file)
@@ -278,7 +278,6 @@ class qtype_multichoice_multi_renderer extends qtype_multichoice_renderer_base {
         if (!empty($right)) {
                 return get_string('correctansweris', 'qtype_multichoice',
                         implode(', ', $right));
-            
         }
         return '';
     }
index 642a971..be394a2 100644 (file)
@@ -74,7 +74,7 @@ $string['unithdr'] = 'Unit {$a}';
 $string['unitmandatory'] = 'Mandatory';
 $string['unitmandatory_help'] = '
 
-* The response will be graded using the unit written.  
+* The response will be graded using the unit written.
 
 * The unit penalty will be applied if the unit field is empty
 
@@ -85,7 +85,7 @@ $string['unitoptional'] = 'Optional unit';
 $string['unitoptional_help'] = '
 * If the unit field is not empty, the response will be graded using this unit.
 
-* If the unit is badly written or unknown, the response will be considered as non valid. 
+* If the unit is badly written or unknown, the response will be considered as non valid.
 ';
 $string['unitused'] = '<STRONG>UNIT USED</STRONG>';
 $string['unituses'] = 'Unit uses';
@@ -109,10 +109,10 @@ $string['validnumberformats'] = 'Valid number formats';
 $string['validnumberformats_help'] = '
 * regular numbers  13500.67 : 13 500.67 : 13500,67: 13 500,67
 
-* if you use , as thousand separator *always* put the decimal . as in 
+* if you use , as thousand separator *always* put the decimal . as in
  13,500.67 : 13,500.
-* for exponent form, say 1.350067 * 10<sup>4</sup>, use  
+
+* for exponent form, say 1.350067 * 10<sup>4</sup>, use
  1.350067 E4 : 1.350067 E04 ';
 
 $string['validnumbers'] = ' 13500.67 : 13 500.67 : 13,500.67 : 13500,67: 13 500,67 : 1.350067 E4 : 1.350067 E04 ';
index b2f919f..b49863b 100644 (file)
@@ -56,7 +56,7 @@ class qtype_opaque_edit_form extends question_edit_form {
         $mform->setType('engineid', PARAM_INT);
         $mform->addRule('engineid', null, 'required', null, 'client');
         $mform->addHelpButton('engineid', 'questionengine', 'qtype_opaque');
-        
+
         $mform->addElement('text', 'remoteid', get_string('questionid', 'qtype_opaque'), array('size' => 50));
         $mform->setType('remoteid', PARAM_RAW);
         $mform->addRule('remoteid', null, 'required', null, 'client');
index d25bbe6..a2b0145 100644 (file)
@@ -77,6 +77,5 @@ class qtype_opaque_locallib_test extends UnitTestCase {
         $engine2->questionengines = array(
                 'http://ltsweb2.open.ac.uk/om-qe/services/Om');
         $this->assertFalse($manager->is_same_engine($engine1, $engine2));
-        
     }
 }
index 898e2a3..1cdb4ea 100644 (file)
@@ -34,8 +34,6 @@
 class qtype_oumultiresponse_edit_form extends question_edit_form {
 
     public function definition_inner($mform) {
-        global $QTYPES;
-
         $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleanswers', 'qtype_multichoice'), null, null, array(0,1));
         $mform->addHelpButton('shuffleanswers', 'shuffleanswers', 'qtype_multichoice');
         $mform->setDefault('shuffleanswers', 1);
index 20a94c6..2c21359 100644 (file)
@@ -780,7 +780,7 @@ class question_type {
 
     /**
      * This method should return all the possible types of response that are
-     * recognised for this question. 
+     * recognised for this question.
      *
      * The question is modelled as comprising one or more subparts. For each
      * subpart, there are one or more classes that that students response
index a4b419f..4807b42 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/>.
+
+
 /**
  * Defines the editing form for the random question type.
  *
@@ -20,7 +37,7 @@ class question_edit_random_form extends question_edit_form {
      * If your question type does not support all these fields, then you can
      * override this method and remove the ones you don't want with $mform->removeElement().
      */
-    function definition() {
+    public function definition() {
         global $COURSE, $CFG;
 
         $qtype = $this->qtype();
@@ -79,19 +96,19 @@ class question_edit_random_form extends question_edit_form {
         $mform->closeHeaderBefore('buttonar');
     }
 
-    function set_data($question) {
+    public function set_data($question) {
         $question->questiontext = array('text' => $question->questiontext);
         // We don't want the complex stuff in the base class to run.
         moodleform::set_data($question);
     }
 
-    function validation($fromform, $files) {
+    public function validation($fromform, $files) {
         //validation of category
         //is not relevant for this question type
         return array();
     }
 
-    function qtype() {
+    public function qtype() {
         return 'random';
     }
 }
index 71422fc..ee00545 100644 (file)
 /**
  * Strings for component 'qtype_random', language 'en', branch 'MOODLE_20_STABLE'
  *
- * @package   qtype_random
- * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package    qtype
+ * @subpackage random
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 $string['configselectmanualquestions'] = 'Can the random question type select a manually graded question when it is making its random choice of a question from a category?';
index 6d99083..ae6da18 100644 (file)
@@ -1,42 +1,68 @@
 <?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/>.
+
+
 /**
- * Class for the random question type.
+ * Question type class for the random question type.
  *
- * The random question type does not have any options. When the question is
- * attempted, it picks a question at random from the category it is in (and
- * optionally its subcategories). For details see create_session_and_responses.
- * Then all other method calls as delegated to that other question.
+ * @package qtype_random
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+/**
+ * The random question type.
+ *
+ * This question type does not have a question definition class, nor any
+ * renderers. When you load a question of this type, it actually loads a
+ * question chosen randomly from a particular category in the question bank.
  *
- * @package questionbank
- * @subpackage questiontypes
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class random_qtype extends default_questiontype {
+class qtype_random extends question_type {
+    /** @var string comma-separated list of qytpe names not to select, can be used in SQL. */
     protected $excludedqtypes = null;
-    protected $manualqtypes = null;
 
-    // Caches questions available as randoms sorted by category
-    // This is a 2-d array. The first key is question category, and the
-    // second is whether to include subcategories.
-    private $catrandoms = array();
+    /** @var string comma-separated list of manually graded qytpe names, can be used in SQL. */
+    protected $manualqtypes = null;
 
-    function name() {
-        return 'random';
-    }
+    /**
+     * Cache of availabe question ids from a particular category.
+     * @var array two-dimensional array. The first key is a category id, the
+     * second key is wether subcategories should be included.
+     */
+    private $availablequestionsbycategory = array();
 
-    function menu_name() {
+    public function menu_name() {
         // Don't include this question type in the 'add new question' menu.
         return false;
     }
 
-    function show_analysis_of_responses() {
+    public function is_manual_graded() {
         return true;
     }
 
-    function is_manual_graded() {
-        return true;
+    public function is_usable_by_random() {
+        return false;
     }
 
-    function is_question_manual_graded($question, $otherquestionsinuse) {
+    public function is_question_manual_graded($question, $otherquestionsinuse) {
         global $DB;
         // We take our best shot at working whether a particular question is manually
         // graded follows: We look to see if any of the questions that this random
@@ -61,30 +87,26 @@ class random_qtype extends default_questiontype {
                      AND qtype IN ($this->manualqtypes)");
     }
 
-    function is_usable_by_random() {
-        return false;
-    }
-
     /**
      * This method needs to be called before the ->excludedqtypes and
      *      ->manualqtypes fields can be used.
      */
-    function init_qtype_lists() {
-        global $QTYPES;
-        if (is_null($this->excludedqtypes)) {
-            $excludedqtypes = array();
-            $manualqtypes = array();
-            foreach ($QTYPES as $qtype) {
-                $quotedname = "'" . $qtype->name() . "'";
-                if (!$qtype->is_usable_by_random()) {
-                    $excludedqtypes[] = $quotedname;
-                } else if ($qtype->is_manual_graded()) {
-                    $manualqtypes[] = $quotedname;
-                }
+    protected function init_qtype_lists() {
+        if (!is_null($this->excludedqtypes)) {
+            return; // Already done.
+        }
+        $excludedqtypes = array();
+        $manualqtypes = array();
+        foreach (question_bank::get_all_qtypes() as $qtype) {
+            $quotedname = "'" . $qtype->name() . "'";
+            if (!$qtype->is_usable_by_random()) {
+                $excludedqtypes[] = $quotedname;
+            } else if ($qtype->is_manual_graded()) {
+                $manualqtypes[] = $quotedname;
             }
-            $this->excludedqtypes = implode(',', $excludedqtypes);
-            $this->manualqtypes = implode(',', $manualqtypes);
         }
+        $this->excludedqtypes = implode(',', $excludedqtypes);
+        $this->manualqtypes = implode(',', $manualqtypes);
     }
 
     function display_question_editing_page(&$mform, $question, $wizardnow){
@@ -94,10 +116,7 @@ class random_qtype extends default_questiontype {
         $mform->display();
     }
 
-    function get_question_options(&$question) {
-        // Don't do anything here, because the random question has no options.
-        // Everything is handled by the create- or restore_session_and_responses
-        // functions.
+    public function get_question_options($question) {
         return true;
     }
 
@@ -108,7 +127,7 @@ class random_qtype extends default_questiontype {
      * @param boolean $includesubcategories whether this question also picks from subcategories.
      * @return string the name this question should have.
      */
-    function question_name($category, $includesubcategories) {
+    public function question_name($category, $includesubcategories) {
         if ($includesubcategories) {
             $string = 'randomqplusname';
         } else {
@@ -117,16 +136,21 @@ class random_qtype extends default_questiontype {
         return get_string($string, 'qtype_random', $category->name);
     }
 
-    function save_question($question, $form) {
-        $form->name = '';
-        // Name is not a required field for random questions, but parent::save_question
-        // Assumes that it is.
-        return parent::save_question($question, $form);
+    protected function set_selected_question_name($question, $randomname) {
+        $a = new stdClass;
+        $a->randomname = $randomname;
+        $a->questionname = $question->name;
+        $question->name = get_string('selectedby', 'qtype_random', $a);
     }
 
-    function save_question_options($question) {
-        global $DB;
+    public function save_question($question, $form, $course) {
+        $form->name = '';
+        // Name is not a required field for random questions, but
+        // parent::save_question Assumes that it is.
+        return parent::save_question($question, $form, $course);
+    }
 
+    public function save_question_options($question) {
         // No options, as such, but we set the parent field to the question's
         // own id. Setting the parent field has the effect of hiding this
         // question in various places.
@@ -135,9 +159,7 @@ class random_qtype extends default_questiontype {
         $updateobject->parent = $question->id;
 
         // We also force the question name to be 'Random (categoryname)'.
-        if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
-            print_error('cannotretrieveqcat', 'question');
-        }
+        $category = $DB->get_record('question_categories', array('id' => $question->category), '*', MUST_EXIST);
         $updateobject->name = $this->question_name($category, !empty($question->questiontext));
         return $DB->update_record('question', $updateobject);
     }
@@ -150,234 +172,54 @@ class random_qtype extends default_questiontype {
      * @param string $questionsinuse comma-separated list of question ids to exclude from consideration.
      * @return array of question records.
      */
-    function get_usable_questions_from_category($categoryid, $subcategories, $questionsinuse) {
-        global $DB;
+    public function get_available_questions_from_category($categoryid, $subcategories) {
+        if (isset($this->availablequestionsbycategory[$categoryid][$subcategories])) {
+            return $this->availablequestionsbycategory[$categoryid][$subcategories];
+        }
+
         $this->init_qtype_lists();
         if ($subcategories) {
-            $categorylist = question_categorylist($categoryid);
+            $categoryids = question_categorylist($categoryid);
         } else {
-            $categorylist = $categoryid;
-        }
-        if (!$catrandoms = $DB->get_records_select('question',
-                "category IN ($categorylist)
-                     AND parent = 0
-                     AND hidden = 0
-                     AND id NOT IN ($questionsinuse)
-                     AND qtype NOT IN ($this->excludedqtypes)", null, '', 'id')) {
-            $catrandoms = array();
-        }
-        return $catrandoms;
-    }
-
-    function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) {
-        global $QTYPES, $DB;
-        // Choose a random question from the category:
-        // We need to make sure that no question is used more than once in the
-        // quiz. Therfore the following need to be excluded:
-        // 1. All questions that are explicitly assigned to the quiz
-        // 2. All random questions
-        // 3. All questions that are already chosen by an other random question
-        // 4. Deleted questions
-        if (!isset($cmoptions->questionsinuse)) {
-            $cmoptions->questionsinuse = $attempt->layout;
-        }
-
-        if (!isset($this->catrandoms[$question->category][$question->questiontext])) {
-            $catrandoms = $this->get_usable_questions_from_category($question->category,
-                    $question->questiontext == "1", $cmoptions->questionsinuse);
-            $this->catrandoms[$question->category][$question->questiontext] = swapshuffle_assoc($catrandoms);
+            $categoryids = $categoryid;
         }
 
-        while ($wrappedquestion = array_pop(
-                $this->catrandoms[$question->category][$question->questiontext])) {
-            if (!preg_match("~(^|,)$wrappedquestion->id(,|$)~", $cmoptions->questionsinuse)) {
-                /// $randomquestion is not in use and will therefore be used
-                /// as the randomquestion here...
-                $wrappedquestion = $DB->get_record('question', array('id' => $wrappedquestion->id));
-                global $QTYPES;
-                $QTYPES[$wrappedquestion->qtype]
-                        ->get_question_options($wrappedquestion);
-                $QTYPES[$wrappedquestion->qtype]
-                        ->create_session_and_responses($wrappedquestion,
-                        $state, $cmoptions, $attempt);
-                $wrappedquestion->name_prefix = $question->name_prefix;
-                $wrappedquestion->maxgrade = $question->maxgrade;
-                $cmoptions->questionsinuse .= ",$wrappedquestion->id";
-                $state->options->question = &$wrappedquestion;
-                return true;
-            }
-        }
-        $question->questiontext = '<span class="notifyproblem">'.
-                get_string('toomanyrandom', 'quiz'). '</span>';
-        $question->qtype = 'description';
-        $state->responses = array('' => '');
-        return true;
+        $questionids = question_bank::get_finder()->get_questions_from_categories(
+                $categoryids, 'qtype NOT IN (' . $this->excludedqtypes . ')');
+        $this->availablequestionsbycategory[$categoryid][$subcategories] = $questionids;
+        return $questionids;
     }
 
-    function restore_session_and_responses(&$question, &$state) {
-        /// The raw response records for random questions come in two flavours:
-        /// ---- 1 ----
-        /// For responses stored by Moodle version 1.5 and later the answer
-        /// field has the pattern random#-* where the # part is the numeric
-        /// question id of the actual question shown in the quiz attempt
-        /// and * represents the student response to that actual question.
-        /// ---- 2 ----
-        /// For responses stored by older Moodle versions - the answer field is
-        /// simply the question id of the actual question. The student response
-        /// to the actual question is stored in a separate response record.
-        /// -----------------------
-        /// This means that prior to Moodle version 1.5, random questions needed
-        /// two response records for storing the response to a single question.
-        /// From version 1.5 and later the question type random works like all
-        /// the other question types in that it now only needs one response
-        /// record per question.
-        global $QTYPES, $DB, $OUTPUT;
-        if (!preg_match('~^random([0-9]+)-(.*)$~', $state->responses[''], $answerregs)) {
-            if (empty($state->responses[''])) {
-                // This is the case if there weren't enough questions available in the category.
-                $question->questiontext = '<span class="notifyproblem">'.
-                 get_string('toomanyrandom', 'quiz'). '</span>';
-                $question->qtype = 'description';
-                return true;
-            }
-            // this must be an old-style state which stores only the id for the wrapped question
-            if (!$wrappedquestion = $DB->get_record('question', array('id' => $state->responses['']))) {
-                echo $OUTPUT->notification("Can not find wrapped question {$state->responses['']}");
-            }
-            // In the old model the actual response was stored in a separate entry in
-            // the state table and fortunately there was only a single state per question
-            if (!$state->responses[''] = $DB->get_field('question_states', 'answer', array('attempt' => $state->attempt, 'question' => $wrappedquestion->id))) {
-                echo $OUTPUT->notification("Wrapped state missing");
-            }
-        } else {
-            if (!$wrappedquestion = $DB->get_record('question', array('id' => $answerregs[1]))) {
-                // The teacher must have deleted this question by mistake
-                // Convert it into a description type question with an explanation to the student
-                $wrappedquestion = clone($question);
-                $wrappedquestion->id = $answerregs[1];
-                $wrappedquestion->questiontext = get_string('questiondeleted', 'quiz');
-                $wrappedquestion->qtype = 'missingtype';
-            }
-            $state->responses[''] = (false === $answerregs[2]) ? '' : $answerregs[2];
-        }
-
-        if (!$QTYPES[$wrappedquestion->qtype]
-         ->get_question_options($wrappedquestion)) {
-            return false;
-        }
-
-        if (!$QTYPES[$wrappedquestion->qtype]
-         ->restore_session_and_responses($wrappedquestion, $state)) {
-            return false;
-        }
-        $wrappedquestion->name_prefix = $question->name_prefix;
-        $wrappedquestion->maxgrade    = $question->maxgrade;
-        $state->options->question = &$wrappedquestion;
-        return true;
+    public function make_question($questiondata) {
+        return $this->choose_other_question($questiondata, array());
     }
 
-    function save_session_and_responses(&$question, &$state) {
-        global $QTYPES, $DB;
-        $wrappedquestion = &$state->options->question;
-
-        // Trick the wrapped question into pretending to be the random one.
-        $realqid = $wrappedquestion->id;
-        $wrappedquestion->id = $question->id;
-        $QTYPES[$wrappedquestion->qtype]
-         ->save_session_and_responses($wrappedquestion, $state);
+    /**
+     * Load the definition of another question picked randomly by this question.
+     * @param object $questiondata the data defining a random question.
+     * @param array $excludedquestions of question ids. We will no pick any question whose id is in this list.
+     * @param boolean $allowshuffle if false, then any shuffle option on the selected quetsion is disabled.
+     * @return question_definition|null the definition of the question that was
+     *      selected, or null if no suitable question could be found.
+     */
+    public function choose_other_question($questiondata, $excludedquestions, $allowshuffle = true) {
+        $available = $this->get_available_questions_from_category($questiondata->category,
+                !empty($questiondata->questiontext));
+        shuffle($available);
+
+        foreach ($available as $questionid) {
+            if (in_array($questionid, $excludedquestions)) {
+                continue;
+            }
 
-        // Read what the wrapped question has just set the answer field to
-        // (if anything)
-        $response = $DB->get_field('question_states', 'answer', array('id' => $state->id));
-        if(false === $response) {
-            return false;
+            $question = question_bank::load_question($questionid, $allowshuffle);
+            $this->set_selected_question_name($question, $questiondata->name);
+            return $question;
         }
-
-        // Prefix the answer field...
-        $response = "random$realqid-$response";
-
-        // ... and save it again.
-        $DB->set_field('question_states', 'answer', $response, array('id' => $state->id));
-
-        // Restore the real id
-        $wrappedquestion->id = $realqid;
-        return true;
+        return null;
     }
 
-    function get_correct_responses(&$question, &$state) {
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->get_correct_responses($wrappedquestion, $state);
+    function get_random_guess_score($questiondata) {
+        return null;
     }
-
-    // ULPGC ecastro
-    function get_all_responses(&$question, &$state){
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->get_all_responses($wrappedquestion, $state);
-    }
-
-    // ULPGC ecastro
-    function get_actual_response(&$question, &$state){
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->get_actual_response($wrappedquestion, $state);
-    }
-
-    function get_html_head_contributions(&$question, &$state) {
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-                ->get_html_head_contributions($wrappedquestion, $state);
-    }
-
-    function print_question(&$question, &$state, &$number, $cmoptions, $options) {
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        $wrappedquestion->randomquestionid = $question->id;
-        $QTYPES[$wrappedquestion->qtype]
-         ->print_question($wrappedquestion, $state, $number, $cmoptions, $options);
-    }
-
-    function grade_responses(&$question, &$state, $cmoptions) {
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->grade_responses($wrappedquestion, $state, $cmoptions);
-    }
-
-    function get_texsource(&$question, &$state, $cmoptions, $type) {
-        global $QTYPES;
-        $wrappedquestion = &$state->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->get_texsource($wrappedquestion, $state, $cmoptions, $type);
-    }
-
-    function compare_responses(&$question, $state, $teststate) {
-        global $QTYPES;
-        $wrappedquestion = &$teststate->options->question;
-        return $QTYPES[$wrappedquestion->qtype]
-         ->compare_responses($wrappedquestion, $state, $teststate);
-    }
-
-    /**
-     * For random question type return empty string which means won't calculate.
-     * @param object $question
-     * @return mixed either a integer score out of 1 that the average random
-     * guess by a student might give or an empty string which means will not
-     * calculate.
-     */
-    function get_random_guess_score($question) {
-        return '';
-    }
-
 }
-//// END OF CLASS ////
-
-//////////////////////////////////////////////////////////////////////////
-//// INITIATION - Without this line the question type is not in use... ///
-//////////////////////////////////////////////////////////////////////////
-question_register_questiontype(new random_qtype());
diff --git a/question/type/random/simpletest/testquestiontype.php b/question/type/random/simpletest/testquestiontype.php
new file mode 100644 (file)
index 0000000..7ff36af
--- /dev/null
@@ -0,0 +1,61 @@
+<?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/>.
+
+
+/**
+ * Unit tests for the random question type class.
+ *
+ * @package qtype_random
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->dirroot . '/question/type/random/questiontype.php');
+
+/**
+ * Unit tests for the random question type class.
+ *
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qtype_random_test extends UnitTestCase {
+    var $qtype;
+
+    public function setUp() {
+        $this->qtype = new qtype_random();
+    }
+
+    public function tearDown() {
+        $this->qtype = null;
+    }
+
+    public function test_name() {
+        $this->assertEqual($this->qtype->name(), 'random');
+    }
+
+    public function test_can_analyse_responses() {
+        $this->assertFalse($this->qtype->can_analyse_responses());
+    }
+
+    public function test_get_random_guess_score() {
+        $this->assertNull($this->qtype->get_random_guess_score(null));
+    }
+
+    public function test_get_possible_responses() {
+        $this->assertEqual(array(), $this->qtype->get_possible_responses(null));
+    }
+}
index a0286a2..6af7ac5 100644 (file)
@@ -121,5 +121,4 @@ class question_hint_test extends UnitTestCase {
         $this->assertTrue($hint->shownumcorrect);
         $this->assertTrue($hint->clearwrong);
     }
-    
 }