Skip to content
Snippets Groups Projects
Commit 2bd0701e authored by Tim Hunt's avatar Tim Hunt
Browse files

Moodle XML format import

This commit also includes some bug-fixes to export.

Also, it refactors the code for loading and saving question tests.
parent 297b16e5
No related branches found
No related tags found
No related merge requests found
......@@ -36,7 +36,7 @@ These are the major tasks we still need to complete in approximate order and imp
1. Make sure that STACK questions work as well as possible in the standard Moodle reports.
2. Consider what additional custom STACK reporting we need.
2. **DONE** Implement the Moodle backup/restore code for stack questions.
3. Implement Moodle XML format import and export.
3. **DONE** Implement Moodle XML format import and export.
4. Investigate ways of running Maxima on a separate server.
5. Implement random seed control like for varnumeric.
......
......@@ -38,8 +38,8 @@ $testcase = required_param('testcase', PARAM_INT);
$questiondata = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST);
$question = question_bank::load_question($questionid);
$context = $question->get_context();
$testcasedata = $DB->get_record('qtype_stack_qtests',
array('questionid' => $question->id, 'testcase' => $testcase), '*', MUST_EXIST);
$DB->get_record('qtype_stack_qtests', array('questionid' => $question->id, 'testcase' => $testcase),
'*', MUST_EXIST); // Just to verify that the record exists.
// Check permissions.
require_login();
......@@ -60,12 +60,7 @@ $title = get_string('deletetestcase', 'qtype_stack',
if (data_submitted() && confirm_sesskey()) {
// User has confirmed. Actually delete the test case.
$DB->delete_records('qtype_stack_qtest_expected',
array('questionid' => $questionid, 'testcase' => $testcase));
$DB->delete_records('qtype_stack_qtest_inputs',
array('questionid' => $questionid, 'testcase' => $testcase));
$DB->delete_records('qtype_stack_qtests',
array('questionid' => $questionid, 'testcase' => $testcase));
question_bank::get_qtype('stack')->delete_question_test($questionid, $testcase);
redirect($backurl);
}
......
......@@ -39,11 +39,7 @@ $questiondata = $DB->get_record('question', array('id' => $questionid), '*', MUS
$question = question_bank::load_question($questionid);
$context = $question->get_context();
if ($testcase) {
$testcasedata = $DB->get_record('qtype_stack_qtests',
array('questionid' => $question->id, 'testcase' => $testcase), '*', MUST_EXIST);
} else {
$testcasedata = new stdClass();
$testcasedata->questionid = $question->id;
$qtest = question_bank::get_qtype('stack')->load_question_test($questionid, $testcase);
}
// Check permissions.
......@@ -76,23 +72,18 @@ if (!is_null($testcase)) {
$mform = new qtype_stack_question_test_form($PAGE->url,
array('submitlabel' => $submitlabel, 'question' => $question));
// Load current data.
// Send current data to the form.
if ($testcase) {
$currentdata = new stdClass();
$inputs = $DB->get_records_menu('qtype_stack_qtest_inputs',
array('questionid' => $question->id, 'testcase' => $testcase), 'inputname', 'inputname, value');
foreach ($inputs as $name => $value) {
foreach ($qtest->inputs as $name => $value) {
$currentdata->{$name} = $value;
}
$expectations = $DB->get_records('qtype_stack_qtest_expected',
array('questionid' => $question->id, 'testcase' => $testcase), 'prtname',
'prtname, expectedscore, expectedpenalty, expectedanswernote');
foreach ($expectations as $prtname => $expected) {
$currentdata->{$prtname . 'score'} = $expected->expectedscore + 0;
$currentdata->{$prtname . 'penalty'} = $expected->expectedpenalty + 0;
$currentdata->{$prtname . 'answernote'} = $expected->expectedanswernote;
foreach ($qtest->expectedresults as $prtname => $expected) {
$currentdata->{$prtname . 'score'} = $expected->score;
$currentdata->{$prtname . 'penalty'} = $expected->penalty;
$currentdata->{$prtname . 'answernote'} = $expected->answernote[0];
}
$mform->set_data($currentdata);
......@@ -105,50 +96,19 @@ if ($mform->is_cancelled()) {
} else if ($data = $mform->get_data()) {
// Process form submission.
$transaction = $DB->start_delegated_transaction();
if (!$testcase) {
// Find the first unused testcase number.
$testcase = $DB->get_field_sql('
SELECT MIN(qt.testcase) + 1
FROM (
SELECT testcase FROM {qtype_stack_qtests} WHERE questionid = ?
UNION
SELECT 0
) qt
LEFT JOIN {qtype_stack_qtests} qt2 ON qt2.questionid = ? AND
qt2.testcase = qt.testcase + 1
WHERE qt2.id IS NULL
', array($questionid, $questionid));
$testcasedata->testcase = $testcase;
$DB->insert_record('qtype_stack_qtests', $testcasedata);
}
// Save the input data.
$DB->delete_records('qtype_stack_qtest_inputs', array('questionid' => $question->id, 'testcase' => $testcase));
$inputs = array();
foreach ($question->inputs as $inputname => $notused) {
$testinput = new stdClass();
$testinput->questionid = $question->id;
$testinput->testcase = $testcase;
$testinput->inputname = $inputname;
$testinput->value = $data->$inputname;
$DB->insert_record('qtype_stack_qtest_inputs', $testinput);
$inputs[$inputname] = $data->$inputname;
}
$qtest = new stack_question_test($inputs);
// Save the expected outcome data.
$DB->delete_records('qtype_stack_qtest_expected', array('questionid' => $question->id, 'testcase' => $testcase));
foreach ($question->prts as $prtname => $notused) {
$expected = new stdClass();
$expected->questionid = $question->id;
$expected->testcase = $testcase;
$expected->prtname = $prtname;
$expected->expectedscore = $data->{$prtname . 'score'};
$expected->expectedpenalty = $data->{$prtname . 'penalty'};
$expected->expectedanswernote = $data->{$prtname . 'answernote'};
$DB->insert_record('qtype_stack_qtest_expected', $expected);
$qtest->add_expected_result($prtname, new stack_potentialresponse_tree_state(
'', array(), array($data->{$prtname . 'answernote'}), true,
$data->{$prtname . 'score'}, $data->{$prtname . 'penalty'}));
}
$transaction->allow_commit();
question_bank::get_qtype('stack')->save_question_test($questionid, $qtest, $testcase);
redirect($backurl);
}
......
......@@ -83,7 +83,7 @@ $options->flags = question_display_options::HIDDEN;
$options->suppressruntestslink = true;
// Load the list of test cases.
$testscases = question_bank::get_qtype('stack')->load_question_tests($questiondata);
$testscases = question_bank::get_qtype('stack')->load_question_tests($question->id);
// Exectue the tests.
$testresults = array();
......@@ -148,6 +148,7 @@ if (!$variantmatched) {
array('class' => 'undeployedvariant'));
}
if ($question->has_random_variants()) {
echo html_writer::start_tag('form', array('method' => 'get', 'class' => 'switchtovariant',
'action' => new moodle_url('/question/type/stack/questiontestrun.php')));
echo html_writer::start_tag('p');
......@@ -160,7 +161,7 @@ echo ' ' . html_writer::empty_tag('input', array('type' => 'submit', 'value' =>
echo html_writer::end_tag('p');
echo html_writer::end_tag('form');
}
// Display the question.
echo $OUTPUT->heading(get_string('questionpreview', 'qtype_stack'), 3);
......
This diff is collapsed.
......@@ -618,7 +618,7 @@ class qtype_stack_test_helper extends question_test_helper {
$qdata->name = 'test-0';
$qdata->questiontext = 'What is $1+1$? [[input:ans1]]
[[validation:ans1]]';
$qdata->generalfeedback = 'Generalfeedback: {={a} + {b}} is the right answer.';
$qdata->generalfeedback = '';
$qdata->options = new stdClass();
$qdata->options->id = 0;
......@@ -696,7 +696,7 @@ class qtype_stack_test_helper extends question_test_helper {
$qtest = new stack_question_test(array('ans1' => '2'));
$qtest->add_expected_result('prt1', new stack_potentialresponse_tree_state(
'', array(), array(''), true, 1, 0));
'', array(), array('prt1-1-T'), true, 1, 0));
$qdata->testcases[1] = $qtest;
return $qdata;
......
......@@ -75,7 +75,7 @@ class qtype_stack_test extends UnitTestCase {
[[validation:ans1]]</text>
</questiontext>
<generalfeedback format="html">
<text>Generalfeedback: {={a} + {b}} is the right answer.</text>
<text></text>
</generalfeedback>
<defaultgrade>1</defaultgrade>
<penalty>0.3333333</penalty>
......@@ -151,7 +151,7 @@ class qtype_stack_test extends UnitTestCase {
</falsefeedback>
</node>
</prt>
<deployedseed>{deployedseed}</deployedseed>
<deployedseed>12345</deployedseed>
<qtest>
<testcase>1</testcase>
<testinput>
......@@ -162,7 +162,7 @@ class qtype_stack_test extends UnitTestCase {
<name>prt1</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote></expectedanswernote>
<expectedanswernote>prt1-1-T</expectedanswernote>
</expected>
</qtest>
</question>
......@@ -170,4 +170,184 @@ class qtype_stack_test extends UnitTestCase {
$this->assert_same_xml($expectedxml, $xml);
}
public function test_xml_import() {
$xml = '<!-- question: 0 -->
<question type="stack">
<name>
<text>test-0</text>
</name>
<questiontext format="html">
<text>What is $1+1$? [[input:ans1]]
[[validation:ans1]]</text>
</questiontext>
<generalfeedback format="html">
<text></text>
</generalfeedback>
<defaultgrade>1</defaultgrade>
<penalty>0.3333333</penalty>
<hidden>0</hidden>
<questionvariables>
<text></text>
</questionvariables>
<specificfeedback format="html">
<text>[[feedback:firsttree]]</text>
</specificfeedback>
<questionnote>
<text></text>
</questionnote>
<questionsimplify>1</questionsimplify>
<assumepositive>0</assumepositive>
<markmode>penalty</markmode>
<prtcorrect format="html">
<text><![CDATA[<p>Correct answer, well done.</p>]]></text>
</prtcorrect>
<prtpartiallycorrect format="html">
<text><![CDATA[<p>Your answer is partially correct.</p>]]></text>
</prtpartiallycorrect>
<prtincorrect format="html">
<text><![CDATA[<p>Incorrect answer.</p>]]></text>
</prtincorrect>
<multiplicationsign>dot</multiplicationsign>
<sqrtsign>1</sqrtsign>
<complexno>i</complexno>
<input>
<name>ans1</name>
<type>algebraic</type>
<tans>2</tans>
<boxsize>5</boxsize>
<strictsyntax>1</strictsyntax>
<insertstars>0</insertstars>
<syntaxhint></syntaxhint>
<forbidwords></forbidwords>
<forbidfloat>1</forbidfloat>
<requirelowestterms>0</requirelowestterms>
<checkanswertype>0</checkanswertype>
<mustverify>1</mustverify>
<showvalidation>1</showvalidation>
</input>
<prt>
<name>prt1</name>
<value>1</value>
<autosimplify>1</autosimplify>
<feedbackvariables>
<text></text>
</feedbackvariables>
<node>
<name>0</name>
<answertest>EqualComAss</answertest>
<sans>ans1</sans>
<tans>2</tans>
<testoptions></testoptions>
<quiet>0</quiet>
<truescoremode>=</truescoremode>
<truescore>1</truescore>
<truepenalty>0</truepenalty>
<truenextnode>-1</truenextnode>
<trueanswernote>prt1-1-T</trueanswernote>
<truefeedback format="html">
<text></text>
</truefeedback>
<falsescoremode>=</falsescoremode>
<falsescore>0</falsescore>
<falsepenalty>0</falsepenalty>
<falsenextnode>-1</falsenextnode>
<falseanswernote>prt1-1-F</falseanswernote>
<falsefeedback format="html">
<text></text>
</falsefeedback>
</node>
</prt>
<deployedseed>12345</deployedseed>
<qtest>
<testcase>1</testcase>
<testinput>
<name>ans1</name>
<value>2</value>
</testinput>
<expected>
<name>prt1</name>
<expectedscore>1</expectedscore>
<expectedpenalty>0</expectedpenalty>
<expectedanswernote>prt1-1-T</expectedanswernote>
</expected>
</qtest>
</question>
';
$xmldata = xmlize($xml);
$importer = new qformat_xml();
$q = $importer->try_importing_using_qtypes(
$xmldata['question'], null, null, 'stack');
$expectedq = new stdClass();
$expectedq->qtype = 'stack';
$expectedq->name = 'test-0';
$expectedq->questiontext = 'What is $1+1$? [[input:ans1]]
[[validation:ans1]]';
$expectedq->questiontextformat = FORMAT_HTML;
$expectedq->generalfeedback = '';
$expectedq->generalfeedbackformat = FORMAT_HTML;
$expectedq->defaultmark = 1;
$expectedq->length = 1;
$expectedq->penalty = 0;
$expectedq->questionvariables = '';
$expectedq->specificfeedback = array('text' => '[[feedback:firsttree]]', 'format' => FORMAT_HTML, 'files' => array());
$expectedq->questionnote = '';
$expectedq->questionsimplify = 1;
$expectedq->assumepositive = 0;
$expectedq->markmode = 'penalty';
$expectedq->prtcorrect = array('text' => '<p>Correct answer, well done.</p>', 'format' => FORMAT_HTML, 'files' => array());;
$expectedq->prtpartiallycorrect = array('text' => '<p>Your answer is partially correct.</p>', 'format' => FORMAT_HTML, 'files' => array());;
$expectedq->prtincorrect = array('text' => '<p>Incorrect answer.</p>', 'format' => FORMAT_HTML, 'files' => array());;
$expectedq->multiplicationsign = 'dot';
$expectedq->sqrtsign = 1;
$expectedq->complexno = 'i';
$expectedq->ans1type = 'algebraic';
$expectedq->ans1tans = 2;
$expectedq->ans1boxsize = 5;
$expectedq->ans1strictsyntax = 1;
$expectedq->ans1insertstars = 0;
$expectedq->ans1syntaxhint = '';
$expectedq->ans1forbidwords = '';
$expectedq->ans1forbidfloat = 1;
$expectedq->ans1requirelowestterms = 0;
$expectedq->ans1checkanswertype = 0;
$expectedq->ans1mustverify = 1;
$expectedq->ans1showvalidation = 1;
$expectedq->prt1value = 1;
$expectedq->prt1autosimplify = 1;
$expectedq->prt1feedbackvariables = '';
$expectedq->prt1answertest[0] = 'EqualComAss';
$expectedq->prt1sans[0] = 'ans1';
$expectedq->prt1tans[0] = '2';
$expectedq->prt1testoptions[0] = '';
$expectedq->prt1quiet[0] = 0;
$expectedq->prt1truescoremode[0] = '=';
$expectedq->prt1truescore[0] = 1;
$expectedq->prt1truepenalty[0] = 0;
$expectedq->prt1truenextnode[0] = -1;
$expectedq->prt1trueanswernote[0] = 'prt1-1-T';
$expectedq->prt1truefeedback[0] = array('text' => '', 'format' => FORMAT_HTML, 'files' => array());;;
$expectedq->prt1falsescoremode[0] = '=';
$expectedq->prt1falsescore[0] = 0;
$expectedq->prt1falsepenalty[0] = 0;
$expectedq->prt1falsenextnode[0] = -1;
$expectedq->prt1falseanswernote[0] = 'prt1-1-F';
$expectedq->prt1falsefeedback[0] = array('text' => '', 'format' => FORMAT_HTML, 'files' => array());;;
$expectedq->deployedseeds = array('12345');
$qtest = new stack_question_test(array('ans1' => '2'));
$qtest->add_expected_result('prt1', new stack_potentialresponse_tree_state(
'', array(), array('prt1-1-T'), true, 1, 0));
$expectedq->testcases[1] = $qtest;
$this->assert(new CheckSpecifiedFieldsExpectation($expectedq), $q);
$this->assertEqual($expectedq->deployedseeds, $q->deployedseeds); // Redundant, but gives better fail messages.
$this->assertEqual($expectedq->testcases, $q->testcases); // Redundant, but gives better fail messages.
}
}
......@@ -23,6 +23,7 @@
require_once(dirname(__FILE__) . '/questiontestresult.php');
require_once(dirname(__FILE__) . '/potentialresponsetree.class.php');
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment