mirror of
https://github.com/moodle/moodle.git
synced 2025-08-05 00:46:50 +02:00
MDL-63905 qtype_multianswer: validate imported questions
This commit is contained in:
parent
89d1238962
commit
48aad79a63
18 changed files with 305 additions and 169 deletions
|
@ -51,7 +51,14 @@ class qformat_multianswer extends qformat_default {
|
|||
$questiontext['text'] = implode('', $lines);
|
||||
$questiontext['format'] = FORMAT_MOODLE;
|
||||
$questiontext['itemid'] = '';
|
||||
|
||||
$question = qtype_multianswer_extract_question($questiontext);
|
||||
$errors = qtype_multianswer_validate_question($question);
|
||||
if ($errors) {
|
||||
$this->error(get_string('invalidmultianswerquestion', 'qtype_multianswer', implode(' ', $errors)));
|
||||
return array();
|
||||
}
|
||||
|
||||
$question->questiontext = $question->questiontext['text'];
|
||||
$question->questiontextformat = 0;
|
||||
|
||||
|
|
1
question/format/multianswer/tests/fixtures/broken_multianswer_1.txt
vendored
Normal file
1
question/format/multianswer/tests/fixtures/broken_multianswer_1.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please select the fruits {1:MULTICHOICE:=Apple#Correct}
|
1
question/format/multianswer/tests/fixtures/broken_multianswer_2.txt
vendored
Normal file
1
question/format/multianswer/tests/fixtures/broken_multianswer_2.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please select the fruits {1:MULTICHOICE:Pear#Incorrect~%50%Apple#Correct}
|
1
question/format/multianswer/tests/fixtures/broken_multianswer_3.txt
vendored
Normal file
1
question/format/multianswer/tests/fixtures/broken_multianswer_3.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
What grade would you give it? {3:NUMERICAL:=zero}
|
1
question/format/multianswer/tests/fixtures/broken_multianswer_4.txt
vendored
Normal file
1
question/format/multianswer/tests/fixtures/broken_multianswer_4.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
Please select the fruits.
|
|
@ -75,4 +75,81 @@ The capital of France is {#5}.
|
|||
$this->assertEquals('multichoice', $qs[0]->options->questions[4]->qtype);
|
||||
$this->assertEquals('shortanswer', $qs[0]->options->questions[5]->qtype);
|
||||
}
|
||||
|
||||
public function test_read_brokencloze_1() {
|
||||
$lines = file(__DIR__ . '/fixtures/broken_multianswer_1.txt');
|
||||
$importer = new qformat_multianswer();
|
||||
|
||||
// The importer echoes some errors, so we need to capture and check that.
|
||||
ob_start();
|
||||
$questions = $importer->readquestions($lines);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Check that there were some expected errors.
|
||||
$this->assertContains('Error importing question', $output);
|
||||
$this->assertContains('Invalid embedded answers (Cloze) question', $output);
|
||||
$this->assertContains('This type of question requires at least 2 choices', $output);
|
||||
|
||||
// No question have been imported.
|
||||
$this->assertCount(0, $questions);
|
||||
}
|
||||
|
||||
public function test_read_brokencloze_2() {
|
||||
$lines = file(__DIR__ . '/fixtures/broken_multianswer_2.txt');
|
||||
$importer = new qformat_multianswer();
|
||||
|
||||
// The importer echoes some errors, so we need to capture and check that.
|
||||
ob_start();
|
||||
$questions = $importer->readquestions($lines);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Check that there were some expected errors.
|
||||
$this->assertContains('Error importing question', $output);
|
||||
$this->assertContains('Invalid embedded answers (Cloze) question', $output);
|
||||
$this->assertContains('One of the answers should have a score of 100% so it is possible to get full marks for this question.',
|
||||
$output);
|
||||
|
||||
// No question have been imported.
|
||||
$this->assertCount(0, $questions);
|
||||
}
|
||||
|
||||
public function test_read_brokencloze_3() {
|
||||
$lines = file(__DIR__ . '/fixtures/broken_multianswer_3.txt');
|
||||
$importer = new qformat_multianswer();
|
||||
|
||||
// The importer echoes some errors, so we need to capture and check that.
|
||||
ob_start();
|
||||
$questions = $importer->readquestions($lines);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Check that there were some expected errors.
|
||||
$this->assertContains('Error importing question', $output);
|
||||
$this->assertContains('Invalid embedded answers (Cloze) question', $output);
|
||||
$this->assertContains('The answer must be a number, for example -1.234 or 3e8, or \'*\'.', $output);
|
||||
|
||||
// No question have been imported.
|
||||
$this->assertCount(0, $questions);
|
||||
}
|
||||
|
||||
public function test_read_brokencloze_4() {
|
||||
$lines = file(__DIR__ . '/fixtures/broken_multianswer_4.txt');
|
||||
$importer = new qformat_multianswer();
|
||||
|
||||
// The importer echoes some errors, so we need to capture and check that.
|
||||
ob_start();
|
||||
$questions = $importer->readquestions($lines);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Check that there were some expected errors.
|
||||
$this->assertContains('Error importing question', $output);
|
||||
$this->assertContains('Invalid embedded answers (Cloze) question', $output);
|
||||
$this->assertContains('The question text must include at least one embedded answer.', $output);
|
||||
|
||||
// No question have been imported.
|
||||
$this->assertCount(0, $questions);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ class qformat_xml extends qformat_default {
|
|||
|
||||
// Restore files in generalfeedback.
|
||||
$generalfeedback = $this->import_text_with_files($question,
|
||||
array('#', 'generalfeedback', 0), $qo->generalfeedback, $this->get_format($qo->questiontextformat));
|
||||
array('#', 'generalfeedback', 0), '', $this->get_format($qo->questiontextformat));
|
||||
$qo->generalfeedback = $generalfeedback['text'];
|
||||
$qo->generalfeedbackformat = $generalfeedback['format'];
|
||||
if (!empty($generalfeedback['itemid'])) {
|
||||
|
@ -475,6 +475,11 @@ class qformat_xml extends qformat_default {
|
|||
$questiontext = $this->import_text_with_files($question,
|
||||
array('#', 'questiontext', 0));
|
||||
$qo = qtype_multianswer_extract_question($questiontext);
|
||||
$errors = qtype_multianswer_validate_question($qo);
|
||||
if ($errors) {
|
||||
$this->error(get_string('invalidmultianswerquestion', 'qtype_multianswer', implode(' ', $errors)));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Header parts particular to multianswer.
|
||||
$qo->qtype = 'multianswer';
|
||||
|
@ -483,8 +488,12 @@ class qformat_xml extends qformat_default {
|
|||
if (isset($this->course)) {
|
||||
$qo->course = $this->course;
|
||||
}
|
||||
|
||||
$qo->name = $this->clean_question_name($this->import_text($question['#']['name'][0]['#']['text']));
|
||||
if (isset($question['#']['name'])) {
|
||||
$qo->name = $this->clean_question_name($this->import_text($question['#']['name'][0]['#']['text']));
|
||||
} else {
|
||||
$qo->name = $this->create_default_question_name($qo->questiontext['text'],
|
||||
get_string('questionname', 'question'));
|
||||
}
|
||||
$qo->questiontextformat = $questiontext['format'];
|
||||
$qo->questiontext = $qo->questiontext['text'];
|
||||
if (!empty($questiontext['itemid'])) {
|
||||
|
@ -514,7 +523,7 @@ class qformat_xml extends qformat_default {
|
|||
|
||||
// Restore files in generalfeedback.
|
||||
$generalfeedback = $this->import_text_with_files($question,
|
||||
array('#', 'generalfeedback', 0), $qo->generalfeedback, $this->get_format($qo->questiontextformat));
|
||||
array('#', 'generalfeedback', 0), '', $this->get_format($qo->questiontextformat));
|
||||
$qo->generalfeedback = $generalfeedback['text'];
|
||||
$qo->generalfeedbackformat = $generalfeedback['format'];
|
||||
if (!empty($generalfeedback['itemid'])) {
|
||||
|
@ -937,7 +946,7 @@ class qformat_xml extends qformat_default {
|
|||
* @param stdClass $context
|
||||
* @return array (of objects) question objects.
|
||||
*/
|
||||
protected function readquestions($lines) {
|
||||
public function readquestions($lines) {
|
||||
// We just need it as one big string.
|
||||
$lines = implode('', $lines);
|
||||
|
||||
|
|
82
question/format/xml/tests/fixtures/broken_cloze_questions.xml
vendored
Normal file
82
question/format/xml/tests/fixtures/broken_cloze_questions.xml
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<quiz>
|
||||
<!-- question: 1 -->
|
||||
<question type="cloze">
|
||||
<name>
|
||||
<text>Multianswer question without subquestions</text>
|
||||
</name>
|
||||
<questiontext format="html">
|
||||
<text>Please select the fruits.</text>
|
||||
</questiontext>
|
||||
<generalfeedback format="html">
|
||||
<text></text>
|
||||
</generalfeedback>
|
||||
<penalty>0.3333333</penalty>
|
||||
<hidden>0</hidden>
|
||||
<idnumber></idnumber>
|
||||
</question>
|
||||
|
||||
<!-- question: 2 -->
|
||||
<question type="cloze">
|
||||
<name>
|
||||
<text>Multichoice subquestion without choices</text>
|
||||
</name>
|
||||
<questiontext format="html">
|
||||
<text>Please select the fruits {1:MULTICHOICE}</text>
|
||||
</questiontext>
|
||||
<generalfeedback format="html">
|
||||
<text></text>
|
||||
</generalfeedback>
|
||||
<penalty>0.3333333</penalty>
|
||||
<hidden>0</hidden>
|
||||
<idnumber></idnumber>
|
||||
</question>
|
||||
|
||||
<!-- question: 3 -->
|
||||
<question type="cloze">
|
||||
<name>
|
||||
<text>Multichoice subquestion with only one choice</text>
|
||||
</name>
|
||||
<questiontext format="html">
|
||||
<text>Please select the fruits {1:MULTICHOICE:=Apple#Correct}</text>
|
||||
</questiontext>
|
||||
<generalfeedback format="html">
|
||||
<text></text>
|
||||
</generalfeedback>
|
||||
<penalty>0.3333333</penalty>
|
||||
<hidden>0</hidden>
|
||||
<idnumber></idnumber>
|
||||
</question>
|
||||
|
||||
<!-- question: 4 -->
|
||||
<question type="cloze">
|
||||
<name>
|
||||
<text>Multichoice subquestion with no completely correct answer</text>
|
||||
</name>
|
||||
<questiontext format="html">
|
||||
<text>Please select the fruits {1:MULTICHOICE:Pear#Incorrect~%50%Apple#Correct}</text>
|
||||
</questiontext>
|
||||
<generalfeedback format="html">
|
||||
<text></text>
|
||||
</generalfeedback>
|
||||
<penalty>0.3333333</penalty>
|
||||
<hidden>0</hidden>
|
||||
<idnumber></idnumber>
|
||||
</question>
|
||||
|
||||
<!-- question: 5 -->
|
||||
<question type="cloze">
|
||||
<name>
|
||||
<text>Numerical subquestion with buggy answer</text>
|
||||
</name>
|
||||
<questiontext format="html">
|
||||
<text>What grade would you give it? {3:NUMERICAL:=zero}</text>
|
||||
</questiontext>
|
||||
<generalfeedback format="html">
|
||||
<text></text>
|
||||
</generalfeedback>
|
||||
<penalty>0.3333333</penalty>
|
||||
<hidden>0</hidden>
|
||||
<idnumber></idnumber>
|
||||
</question>
|
||||
</quiz>
|
|
@ -422,4 +422,30 @@ class qformat_xml_import_export_test extends advanced_testcase {
|
|||
$expectedxml = file_get_contents(__DIR__ . '/fixtures/nested_categories_with_questions.xml');
|
||||
$this->assert_same_xml($expectedxml, $qformat->exportprocess());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that bad multianswer questions are not imported.
|
||||
*/
|
||||
public function test_import_broken_multianswer_questions() {
|
||||
$lines = file(__DIR__ . '/fixtures/broken_cloze_questions.xml');
|
||||
$importer = $qformat = new qformat_xml();
|
||||
|
||||
// The importer echoes some errors, so we need to capture and check that.
|
||||
ob_start();
|
||||
$questions = $importer->readquestions($lines);
|
||||
$output = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
// Check that there were some expected errors.
|
||||
$this->assertContains('Error importing question', $output);
|
||||
$this->assertContains('Invalid embedded answers (Cloze) question', $output);
|
||||
$this->assertContains('This type of question requires at least 2 choices', $output);
|
||||
$this->assertContains('The answer must be a number, for example -1.234 or 3e8, or \'*\'.', $output);
|
||||
$this->assertContains('One of the answers should have a score of 100% so it is possible to get full marks for this question.',
|
||||
$output);
|
||||
$this->assertContains('The question text must include at least one embedded answer.', $output);
|
||||
|
||||
// No question have been imported.
|
||||
$this->assertCount(0, $questions);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue