' . "\n";
echo $OUTPUT->heading(get_string('chooseqtypetoadd', 'question'), 3);
@@ -1693,19 +1691,18 @@ function print_choose_qtype_to_add_form($hiddenparams) {
echo '
' . "\n";
echo '
' . get_string('selectaqtypefordescription', 'question') . "
\n";
echo '
' . "\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 "
\n";
echo '
' . "\n";
foreach ($fakeqtypes as $qtype => $localizedname) {
- print_qtype_to_add_option($qtype, $localizedname);
+ print_qtype_to_add_option($qtype);
}
echo "
\n";
echo "
\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 '
' . "\n";
- echo '';
- echo ' ';
+ echo '';
+ echo ' ';
echo '';
$fakequestion = new stdClass;
- $fakequestion->qtype = $qtype;
+ $fakequestion->qtype = $qtype->name();
print_question_icon($fakequestion);
- echo $localizedname . ' ' . get_string($qtype . 'summary', 'qtype_' . $qtype);
+ echo $qtype->menu_name() . ' ' .
+ get_string($qtype->name() . 'summary', 'qtype_' . $qtype->name());
echo " \n";
echo "
\n";
}
diff --git a/question/engine/bank.php b/question/engine/bank.php
index 1c1cb66b6b321..012ad3f7834c6 100644
--- a/question/engine/bank.php
+++ b/question/engine/bank.php
@@ -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 @@ public static function get_qtype($qtypename, $mustexist = true) {
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 @@ public static function qtype_enabled($qtypename) {
* @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 @@ public static function get_all_qtypes() {
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 @@ public function get_questions_from_categories($categoryids, $extraconditions) {
"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;
}
}
diff --git a/question/engine/datalib.php b/question/engine/datalib.php
index 591150022e438..dd67067682062 100644
--- a/question/engine/datalib.php
+++ b/question/engine/datalib.php
@@ -349,7 +349,7 @@ public function load_questions_usages_question_state_summary(qubaid_condition $q
q.id,
summarystate
-ORDER BY
+ORDER BY
qa.slot,
qa.questionid,
q.name,
@@ -695,7 +695,7 @@ public function delete_previews($questionid) {
* @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');
}
diff --git a/question/engine/lib.php b/question/engine/lib.php
index 2293f433a36b7..ddc34341094b6 100644
--- a/question/engine/lib.php
+++ b/question/engine/lib.php
@@ -538,7 +538,7 @@ public static function get_postdata(question_attempt $qa) {
*/
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 @@ public function update_question_flags($postdata = null) {
$qa->set_flagged($flagged);
}
}
-
}
/**
@@ -2174,7 +2173,7 @@ public function get_manual_comment() {
/**
* @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.
diff --git a/question/engine/upgradefromoldqe/upgrade.php b/question/engine/upgradefromoldqe/upgrade.php
index a87a16e87410b..c9290faf4d2af 100644
--- a/question/engine/upgradefromoldqe/upgrade.php
+++ b/question/engine/upgradefromoldqe/upgrade.php
@@ -224,7 +224,7 @@ protected function convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $q
$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 @@ protected function convert_quiz_attempt($quiz, $attempt, $questionsessionsrs, $q
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 @@ protected function explode_answer($answer) {
// 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)) {
diff --git a/question/format/multianswer/format.php b/question/format/multianswer/format.php
index 286854f791597..48766d835b072 100644
--- a/question/format/multianswer/format.php
+++ b/question/format/multianswer/format.php
@@ -47,7 +47,7 @@ function readquestions($lines) {
$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 ;
diff --git a/question/format/xml/format.php b/question/format/xml/format.php
index 1555b865563a7..465a032aacb9d 100644
--- a/question/format/xml/format.php
+++ b/question/format/xml/format.php
@@ -419,7 +419,7 @@ function import_multianswer( $questions ) {
$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
diff --git a/question/todo/diffstat.txt b/question/todo/diffstat.txt
index 0bb48ae71df89..4b70098c8ea61 100644
--- a/question/todo/diffstat.txt
+++ b/question/todo/diffstat.txt
@@ -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 +-
diff --git a/question/type/calculated/datasetitems_form.php b/question/type/calculated/datasetitems_form.php
index e3253d5315c64..201cc28e58565 100644
--- a/question/type/calculated/datasetitems_form.php
+++ b/question/type/calculated/datasetitems_form.php
@@ -87,7 +87,7 @@ function definition() {
$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);
diff --git a/question/type/edit_question_form.php b/question/type/edit_question_form.php
index e29fd1bd75385..c029d19bbd264 100644
--- a/question/type/edit_question_form.php
+++ b/question/type/edit_question_form.php
@@ -90,7 +90,7 @@ public function definition() {
$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 @@ protected function add_interactive_settings($withclearwrong = false, $withshownu
}
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 @@ public function set_data($question) {
}
// 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) {
diff --git a/question/type/essay/renderer.php b/question/type/essay/renderer.php
index 58702d9f2dc62..02f430fffa12f 100644
--- a/question/type/essay/renderer.php
+++ b/question/type/essay/renderer.php
@@ -57,7 +57,7 @@ public function formulation_and_controls(question_attempt $qa,
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');
diff --git a/question/type/gapselect/edit_form_base.php b/question/type/gapselect/edit_form_base.php
index 26c97a013597e..a9ea3329143d5 100644
--- a/question/type/gapselect/edit_form_base.php
+++ b/question/type/gapselect/edit_form_base.php
@@ -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();
diff --git a/question/type/match/db/upgrade.php b/question/type/match/db/upgrade.php
index 625efcc120e40..4ffa61c08cddd 100644
--- a/question/type/match/db/upgrade.php
+++ b/question/type/match/db/upgrade.php
@@ -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();
diff --git a/question/type/match/simpletest/testwalkthrough.php b/question/type/match/simpletest/testwalkthrough.php
index eba0b2c77e241..462a907603b9b 100644
--- a/question/type/match/simpletest/testwalkthrough.php
+++ b/question/type/match/simpletest/testwalkthrough.php
@@ -114,7 +114,6 @@ public function test_deferred_feedback_partial_answer() {
// 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);
diff --git a/question/type/multichoice/edit_multichoice_form.php b/question/type/multichoice/edit_multichoice_form.php
index 59c4555eaa708..4e53efa3a593e 100644
--- a/question/type/multichoice/edit_multichoice_form.php
+++ b/question/type/multichoice/edit_multichoice_form.php
@@ -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);
diff --git a/question/type/multichoice/renderer.php b/question/type/multichoice/renderer.php
index aef9399ec0898..667cb24697579 100644
--- a/question/type/multichoice/renderer.php
+++ b/question/type/multichoice/renderer.php
@@ -278,7 +278,6 @@ public function correct_response(question_attempt $qa) {
if (!empty($right)) {
return get_string('correctansweris', 'qtype_multichoice',
implode(', ', $right));
-
}
return '';
}
diff --git a/question/type/numerical/lang/en/qtype_numerical.php b/question/type/numerical/lang/en/qtype_numerical.php
index 642a971a52c28..be394a253e355 100644
--- a/question/type/numerical/lang/en/qtype_numerical.php
+++ b/question/type/numerical/lang/en/qtype_numerical.php
@@ -74,7 +74,7 @@
$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_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'] = '
UNIT USED ';
$string['unituses'] = 'Unit uses';
@@ -109,10 +109,10 @@
$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
4 , use
+
+* for exponent form, say 1.350067 * 10
4 , 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 ';
diff --git a/question/type/opaque/edit_opaque_form.php b/question/type/opaque/edit_opaque_form.php
index b2f919f63f89d..b49863ba4081a 100644
--- a/question/type/opaque/edit_opaque_form.php
+++ b/question/type/opaque/edit_opaque_form.php
@@ -56,7 +56,7 @@ function definition_inner($mform) {
$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');
diff --git a/question/type/opaque/simpletest/testlocallib.php b/question/type/opaque/simpletest/testlocallib.php
index d25bbe61b1b8f..a2b01450c258a 100644
--- a/question/type/opaque/simpletest/testlocallib.php
+++ b/question/type/opaque/simpletest/testlocallib.php
@@ -77,6 +77,5 @@ function test_is_same_engine() {
$engine2->questionengines = array(
'http://ltsweb2.open.ac.uk/om-qe/services/Om');
$this->assertFalse($manager->is_same_engine($engine1, $engine2));
-
}
}
diff --git a/question/type/oumultiresponse/edit_oumultiresponse_form.php b/question/type/oumultiresponse/edit_oumultiresponse_form.php
index 898e2a3035cef..1cdb4eab79b80 100644
--- a/question/type/oumultiresponse/edit_oumultiresponse_form.php
+++ b/question/type/oumultiresponse/edit_oumultiresponse_form.php
@@ -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);
diff --git a/question/type/questiontype.php b/question/type/questiontype.php
index 20a94c6670a26..2c21359dbbe3a 100644
--- a/question/type/questiontype.php
+++ b/question/type/questiontype.php
@@ -780,7 +780,7 @@ function get_random_guess_score($questiondata) {
/**
* 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
diff --git a/question/type/random/edit_random_form.php b/question/type/random/edit_random_form.php
index a4b419fe995fd..4807b42cc43c8 100644
--- a/question/type/random/edit_random_form.php
+++ b/question/type/random/edit_random_form.php
@@ -1,4 +1,21 @@
.
+
+
/**
* 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 @@ function definition() {
$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';
}
}
diff --git a/question/type/random/lang/en/qtype_random.php b/question/type/random/lang/en/qtype_random.php
index 71422fcdb5932..ee00545e063cf 100644
--- a/question/type/random/lang/en/qtype_random.php
+++ b/question/type/random/lang/en/qtype_random.php
@@ -18,9 +18,10 @@
/**
* 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?';
diff --git a/question/type/random/questiontype.php b/question/type/random/questiontype.php
index 6d9908337b8ef..ae6da187e1704 100644
--- a/question/type/random/questiontype.php
+++ b/question/type/random/questiontype.php
@@ -1,42 +1,68 @@
.
+
+
/**
- * 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 @@ function is_question_manual_graded($question, $otherquestionsinuse) {
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 @@ function display_question_editing_page(&$mform, $question, $wizardnow){
$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 @@ function get_question_options(&$question) {
* @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 @@ function question_name($category, $includesubcategories) {
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 @@ function save_question_options($question) {
$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 @@ function save_question_options($question) {
* @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 = '
'.
- get_string('toomanyrandom', 'quiz'). ' ';
- $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 = '
'.
- get_string('toomanyrandom', 'quiz'). ' ';
- $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
index 0000000000000..7ff36afb38245
--- /dev/null
+++ b/question/type/random/simpletest/testquestiontype.php
@@ -0,0 +1,61 @@
+.
+
+
+/**
+ * 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));
+ }
+}
diff --git a/question/type/simpletest/testquestionbase.php b/question/type/simpletest/testquestionbase.php
index a0286a2f211a0..6af7ac5d29b67 100644
--- a/question/type/simpletest/testquestionbase.php
+++ b/question/type/simpletest/testquestionbase.php
@@ -121,5 +121,4 @@ public function test_with_parts() {
$this->assertTrue($hint->shownumcorrect);
$this->assertTrue($hint->clearwrong);
}
-
}