Skip to content

Commit

Permalink
MDL-29644 qtype multianswer, hints lost doing Moodle XML export.
Browse files Browse the repository at this point in the history
This commit also includes a lot of useful tidying up of some of the unit
test helper code. I turned out that I could not use all the helper code
in my new tests, but despite that it will be useful in the future, so I
am committing it here.
  • Loading branch information
timhunt committed Oct 4, 2011
1 parent a449953 commit 77d0f5f
Show file tree
Hide file tree
Showing 5 changed files with 440 additions and 101 deletions.
97 changes: 93 additions & 4 deletions question/engine/simpletest/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,40 @@ public static function initialise_a_question($q) {
$q->modifiedby = $USER->id;
}

public static function initialise_question_data($qdata) {
global $USER;

$qdata->id = 0;
$qdata->category = 0;
$qdata->contextid = 0;
$qdata->parent = 0;
$qdata->questiontextformat = FORMAT_HTML;
$qdata->generalfeedbackformat = FORMAT_HTML;
$qdata->defaultmark = 1;
$qdata->penalty = 0.3333333;
$qdata->length = 1;
$qdata->stamp = make_unique_id_code();
$qdata->version = make_unique_id_code();
$qdata->hidden = 0;
$qdata->timecreated = time();
$qdata->timemodified = time();
$qdata->createdby = $USER->id;
$qdata->modifiedby = $USER->id;
$qdata->hints = array();
}

public static function initialise_question_form_data($qdata) {
$formdata = new stdClass();
$formdata->id = 0;
$formdata->category = '0,0';
$formdata->usecurrentcat = 1;
$formdata->categorymoveto = '0,0';
$formdata->tags = array();
$formdata->penalty = 0.3333333;
$formdata->questiontextformat = FORMAT_HTML;
$formdata->generalfeedbackformat = FORMAT_HTML;
}

/**
* Get the test helper class for a particular question type.
* @param $qtype the question type name, e.g. 'multichoice'.
Expand Down Expand Up @@ -143,7 +177,16 @@ public static function get_test_helper($qtype) {
return self::$testhelpers[$qtype];
}

public static function make_question($qtype, $which = null) {
/**
* Call a method on a qtype_{$qtype}_test_helper class and return the result.
*
* @param string $methodtemplate e.g. 'make_{qtype}_question_{which}';
* @param string $qtype the question type to get a test question for.
* @param string $which one of the names returned by the get_test_questions
* method of the relevant qtype_{$qtype}_test_helper class.
* @param unknown_type $which
*/
protected static function call_question_helper_method($methodtemplate, $qtype, $which = null) {
$helper = self::get_test_helper($qtype);

$available = $helper->get_test_questions();
Expand All @@ -152,18 +195,64 @@ public static function make_question($qtype, $which = null) {
$which = reset($available);
} else if (!in_array($which, $available)) {
throw new coding_exception('Example question ' . $which . ' of type ' .
$qtype . ' does not exist.');
$qtype . ' does not exist.');
}

$method = "make_{$qtype}_question_{$which}";
$method = str_replace(array('{qtype}', '{which}'),
array($qtype, $which), $methodtemplate);

if (!method_exists($helper, $method)) {
throw new coding_exception('Method ' . $method . ' does not exist on the' .
$qtype . ' question type test helper class.');
$qtype . ' question type test helper class.');
}

return $helper->$method();
}

/**
* Question types can provide a number of test question defintions.
* They do this by creating a qtype_{$qtype}_test_helper class that extends
* question_test_helper. The get_test_questions method returns the list of
* test questions available for this question type.
*
* @param string $qtype the question type to get a test question for.
* @param string $which one of the names returned by the get_test_questions
* method of the relevant qtype_{$qtype}_test_helper class.
* @return question_definition the requested question object.
*/
public static function make_question($qtype, $which = null) {
return self::call_question_helper_method('make_{qtype}_question_{which}',
$qtype, $which);
}

/**
* Like {@link make_question()} but returns the datastructure from
* get_question_options instead of the question_definition object.
*
* @param string $qtype the question type to get a test question for.
* @param string $which one of the names returned by the get_test_questions
* method of the relevant qtype_{$qtype}_test_helper class.
* @return stdClass the requested question object.
*/
public static function get_question_data($qtype, $which = null) {
return self::call_question_helper_method('get_{qtype}_question_data_{which}',
$qtype, $which);
}

/**
* Like {@link make_question()} but returns the data what would be saved from
* the question editing form instead of the question_definition object.
*
* @param string $qtype the question type to get a test question for.
* @param string $which one of the names returned by the get_test_questions
* method of the relevant qtype_{$qtype}_test_helper class.
* @return stdClass the requested question object.
*/
public static function get_question_form_data($qtype, $which = null) {
return self::call_question_helper_method('get_{qtype}_question_form_data_{which}',
$qtype, $which);
}

/**
* Makes a multichoice question with choices 'A', 'B' and 'C' shuffled. 'A'
* is correct, defaultmark 1.
Expand Down
145 changes: 55 additions & 90 deletions question/format/xml/format.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,34 +408,38 @@ public function import_multichoice($question) {
* @param array question question array from xml tree
* @return object question object
*/
public function import_multianswer($questions) {
public function import_multianswer($question) {
question_bank::get_qtype('multianswer');

$questiontext = array();
$questiontext['text'] = $this->import_text($questions['#']['questiontext'][0]['#']['text']);
$questiontext['text'] = $this->import_text($question['#']['questiontext'][0]['#']['text']);
$questiontext['format'] = '1';
$questiontext['itemid'] = '';
$qo = qtype_multianswer_extract_question($questiontext);

// 'header' parts particular to multianswer
$qo->qtype = MULTIANSWER;
$qo->qtype = 'multianswer';
$qo->course = $this->course;
$qo->generalfeedback = '';
// restore files in generalfeedback
$qo->generalfeedback = $this->getpath($questions,
$qo->generalfeedback = $this->getpath($question,
array('#', 'generalfeedback', 0, '#', 'text', 0, '#'), $qo->generalfeedback, true);
$qo->generalfeedbackformat = $this->trans_format($this->getpath($questions,
$qo->generalfeedbackformat = $this->trans_format($this->getpath($question,
array('#', 'generalfeedback', 0, '@', 'format'), 'moodle_auto_format'));
$qo->generalfeedbackfiles = $this->import_files($this->getpath($questions,
$qo->generalfeedbackfiles = $this->import_files($this->getpath($question,
array('#', 'generalfeedback', 0, '#', 'file'), array(), false));

if (!empty($questions)) {
$qo->name = $this->import_text($questions['#']['name'][0]['#']['text']);
}
$qo->questiontext = $qo->questiontext['text'];
$qo->name = $this->import_text($question['#']['name'][0]['#']['text']);
$qo->questiontext = $qo->questiontext['text'];
$qo->questiontextformat = '';

$this->import_hints($qo, $questions, true);
$qo->penalty = $this->getpath($question,
array('#', 'penalty', 0, '#'), $this->defaultquestion()->penalty);
// Fix problematic rounding from old files:
if (abs($qo->penalty - 0.3333333) < 0.005) {
$qo->penalty = 0.3333333;
}

$this->import_hints($qo, $question);

return $qo;
}
Expand Down Expand Up @@ -633,12 +637,12 @@ public function import_numerical($question) {
* @param array question question array from xml tree
* @return object question object
*/
public function import_matching($question) {
public function import_match($question) {
// get common parts
$qo = $this->import_headers($question);

// header parts particular to matching
$qo->qtype = MATCH;
$qo->qtype = 'match';
$qo->shuffleanswers = $this->trans_single($this->getpath($question,
array('#', 'shuffleanswers', 0, '#'), 1));

Expand Down Expand Up @@ -900,9 +904,9 @@ protected function readquestions($lines) {
$qo = $this->import_numerical($question);
} else if ($questiontype == 'description') {
$qo = $this->import_description($question);
} else if ($questiontype == 'matching') {
$qo = $this->import_matching($question);
} else if ($questiontype == 'cloze') {
} else if ($questiontype == 'matching' || $questiontype == 'match') {
$qo = $this->import_match($question);
} else if ($questiontype == 'cloze' || $questiontype == 'multianswer') {
$qo = $this->import_multianswer($question);
} else if ($questiontype == 'essay') {
$qo = $this->import_essay($question);
Expand Down Expand Up @@ -942,34 +946,21 @@ public function export_file_extension() {
}

/**
* Turn the internal question code into a human readable form
* (The code used to be numeric, but this remains as some of
* the names don't match the new internal format)
* @param mixed $typeid Internal code
* @return string question type string
* Turn the internal question type name into a human readable form.
* (In the past, the code used to use integers internally. Now, it uses
* strings, so there is less need for this, but to maintain
* backwards-compatibility we change two of the type names.)
* @param string $qtype question type plugin name.
* @return string $qtype string to use in the file.
*/
protected function get_qtype($typeid) {
switch($typeid) {
case TRUEFALSE:
return 'truefalse';
case MULTICHOICE:
return 'multichoice';
case SHORTANSWER:
return 'shortanswer';
case NUMERICAL:
return 'numerical';
case MATCH:
protected function get_qtype($qtype) {
switch($qtype) {
case 'match':
return 'matching';
case DESCRIPTION:
return 'description';
case MULTIANSWER:
case 'multianswer':
return 'cloze';
case ESSAY:
return 'essay';
case CALCULATED:
return 'calculated';
default:
return false;
return $qtype;
}
}

Expand Down Expand Up @@ -1085,13 +1076,9 @@ public function writequestion($question) {
$expout .= "<!-- question: $question->id -->\n";

// Check question type
if (!$questiontype = $this->get_qtype($question->qtype)) {
// must be a plugin then, so just accept the name supplied
$questiontype = $question->qtype;
}
$questiontype = $this->get_qtype($question->qtype);

// add opening tag
// generates specific header for Cloze and category type question
// Categories are a special case.
if ($question->qtype == 'category') {
$categorypath = $this->writetext($question->category);
$expout .= " <question type=\"category\">\n";
Expand All @@ -1100,47 +1087,29 @@ public function writequestion($question) {
$expout .= " </category>\n";
$expout .= " </question>\n";
return $expout;
}

} else if ($question->qtype != MULTIANSWER) {
// for all question types except Close
$name_text = $this->writetext($question->name, 3);

$expout .= " <question type=\"$questiontype\">\n";
$expout .= " <name>\n";
$expout .= $name_text;
$expout .= " </name>\n";
$expout .= " <questiontext {$this->format($question->questiontextformat)}>\n";
$expout .= $this->writetext($question->questiontext, 3);
$expout .= $this->writefiles($question->questiontextfiles);
$expout .= " </questiontext>\n";
$expout .= " <generalfeedback {$this->format($question->generalfeedbackformat)}>\n";
$expout .= $this->writetext($question->generalfeedback, 3);
$expout .= $this->writefiles($question->generalfeedbackfiles);
$expout .= " </generalfeedback>\n";
// Now we know we are are handing a real question.
// Output the generic information.
$expout .= " <question type=\"$questiontype\">\n";
$expout .= " <name>\n";
$expout .= $this->writetext($question->name, 3);
$expout .= " </name>\n";
$expout .= " <questiontext {$this->format($question->questiontextformat)}>\n";
$expout .= $this->writetext($question->questiontext, 3);
$expout .= $this->writefiles($question->questiontextfiles);
$expout .= " </questiontext>\n";
$expout .= " <generalfeedback {$this->format($question->generalfeedbackformat)}>\n";
$expout .= $this->writetext($question->generalfeedback, 3);
$expout .= $this->writefiles($question->generalfeedbackfiles);
$expout .= " </generalfeedback>\n";
if ($question->qtype != 'multianswer') {
$expout .= " <defaultgrade>{$question->defaultmark}</defaultgrade>\n";
$expout .= " <penalty>{$question->penalty}</penalty>\n";
$expout .= " <hidden>{$question->hidden}</hidden>\n";

} else {
// for Cloze type only
$name_text = $this->writetext($question->name);
$question_text = $this->writetext($question->questiontext);
$generalfeedback = $this->writetext($question->generalfeedback);
$expout .= " <question type=\"$questiontype\">\n";
$expout .= " <name>\n";
$expout .= $name_text;
$expout .= " </name>\n";
$expout .= " <questiontext>\n";
$expout .= $this->writetext($question->questiontext, 3);
$expout .= $this->writefiles($question->questiontextfiles);
$expout .= " </questiontext>\n";
$expout .= " <generalfeedback>\n";
$expout .= $this->writetext($question->generalfeedback, 3);
$expout .= $this->writefiles($question->generalfeedbackfiles);
$expout .= " </generalfeedback>\n";
}
$expout .= " <penalty>{$question->penalty}</penalty>\n";
$expout .= " <hidden>{$question->hidden}</hidden>\n";

// output depends on question type
// The rest of the output depends on question type.
switch($question->qtype) {
case 'category':
// not a qtype really - dummy used for category switching
Expand Down Expand Up @@ -1238,12 +1207,8 @@ public function writequestion($question) {
break;

case 'multianswer':
$acount = 1;
foreach ($question->options->questions as $question) {
$thispattern = "{#".$acount."}";
$thisreplace = $question->questiontext;
$expout = preg_replace("~$thispattern~", $thisreplace, $expout);
$acount++;
foreach ($question->options->questions as $index => $subq) {
$expout = preg_replace('~{#' . $index . '}~', $subq->questiontext, $expout);
}
break;

Expand Down
Loading

0 comments on commit 77d0f5f

Please sign in to comment.