From 88bc20c30fdc589c3265dcbe64ea9eba15703037 Mon Sep 17 00:00:00 2001 From: tjhunt Date: Wed, 30 Sep 2009 13:38:36 +0000 Subject: [PATCH] question types: MDL-20157 export_to_xml and import_from_xml functions for question types using extra_question_fields. Thanks to Oleg Sychev for the implementation. --- question/format.php | 38 ++++++++++----- question/format/xml/format.php | 74 ++++++++++++++--------------- question/type/questiontype.php | 86 +++++++++++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 52 deletions(-) diff --git a/question/format.php b/question/format.php index 1356844ca2..74d40c8aa5 100644 --- a/question/format.php +++ b/question/format.php @@ -46,9 +46,9 @@ class qformat_default { * @param object category the category object */ function setCategory( $category ) { - if (count($this->questions)){ - debugging('You shouldn\'t call setCategory after setQuestions'); - } + if (count($this->questions)){ + debugging('You shouldn\'t call setCategory after setQuestions'); + } $this->category = $category; } @@ -59,9 +59,9 @@ class qformat_default { * @param array of question objects */ function setQuestions( $questions ) { - if ($this->category !== null){ - debugging('You shouldn\'t call setQuestions after setCategory'); - } + if ($this->category !== null){ + debugging('You shouldn\'t call setQuestions after setCategory'); + } $this->questions = $questions; } @@ -88,15 +88,15 @@ class qformat_default { function setFilename( $filename ) { $this->filename = $filename; } - - /** + + /** * set the "real" filename * (this is what the user typed, regardless of wha happened next) * @param string realfilename name of file as typed by user */ function setRealfilename( $realfilename ) { - $this->realfilename = $realfilename; - } + $this->realfilename = $realfilename; + } /** * set matchgrades @@ -181,15 +181,27 @@ class qformat_default { * @param data mixed The segment of data containing the question * @param question object processed (so far) by standard import code if appropriate * @param extra mixed any additional format specific data that may be passed by the format + * @param qtypehint hint about a question type from format * @return object question object suitable for save_options() or false if cannot handle */ - function try_importing_using_qtypes( $data, $question=null, $extra=null ) { + function try_importing_using_qtypes( $data, $question=null, $extra=null, $qtypehint='') { global $QTYPES; // work out what format we are using - $formatname = substr( get_class( $this ), strlen('qformat_')); + $formatname = substr(get_class($this), strlen('qformat_')); $methodname = "import_from_$formatname"; + //first try importing using a hint from format + if (!empty($qtypehint)) { + $qtype = $QTYPES[$qtypehint]; + if (is_object($qtype) && method_exists($qtype, $methodname)) { + $question = $qtype->$methodname($data, $question, $this, $extra); + if ($question) { + return $question; + } + } + } + // loop through installed questiontypes checking for // function to handle this question foreach ($QTYPES as $qtype) { @@ -377,7 +389,7 @@ class qformat_default { $catnames = explode($delimiter, $catpath); $parent = 0; $category = null; - + // check for context id in path, it might not be there in pre 1.9 exports $matchcount = preg_match('/^\$([a-z]+)\$$/', $catnames[0], $matches); if ($matchcount==1) { diff --git a/question/format/xml/format.php b/question/format/xml/format.php index 582b869c6a..1924328d5d 100755 --- a/question/format/xml/format.php +++ b/question/format/xml/format.php @@ -23,15 +23,15 @@ class qformat_xml extends qformat_default { // IMPORT FUNCTIONS START HERE - /** + /** * Translate human readable format name * into internal Moodle code number * @param string name format name from xml file * @return int Moodle format code */ function trans_format( $name ) { - $name = trim($name); - + $name = trim($name); + if ($name=='moodle_auto_format') { $id = 0; } @@ -86,8 +86,8 @@ class qformat_xml extends qformat_default { * return the value of a node, given a path to the node * if it doesn't exist return the default value * @param array xml data to read - * @param array path path to node expressed as array - * @param mixed default + * @param array path path to node expressed as array + * @param mixed default * @param bool istext process as text * @param string error if set value must exist, return false and issue message if not * @return mixed value @@ -148,7 +148,7 @@ class qformat_xml extends qformat_default { * import the common parts of a single answer * @param array answer xml tree for single answer * @return object answer object - */ + */ function import_answer( $answer ) { $fraction = $this->getpath( $answer, array('@','fraction'),0 ); $text = $this->getpath( $answer, array('#','text',0,'#'), '', true ); @@ -162,7 +162,7 @@ class qformat_xml extends qformat_default { } /** - * import multiple choice question + * import multiple choice question * @param array question question array from xml tree * @return object question object */ @@ -187,7 +187,7 @@ class qformat_xml extends qformat_default { } // run through the answers - $answers = $question['#']['answer']; + $answers = $question['#']['answer']; $a_count = 0; foreach ($answers as $answer) { $ans = $this->import_answer( $answer ); @@ -247,7 +247,7 @@ class qformat_xml extends qformat_default { if ($answertext != 'true' && $answertext != 'false') { $warning = true; $answertext = $first ? 'true' : 'false'; // Old style file, assume order is true/false. - } + } if ($answertext == 'true') { $qo->answer = ($answer['@']['fraction'] == 100); $qo->correctanswer = $qo->answer; @@ -285,7 +285,7 @@ class qformat_xml extends qformat_default { $qo->usecase = $this->getpath($question, array('#','usecase',0,'#'), $qo->usecase ); // run through the answers - $answers = $question['#']['answer']; + $answers = $question['#']['answer']; $a_count = 0; foreach ($answers as $answer) { $ans = $this->import_answer( $answer ); @@ -297,7 +297,7 @@ class qformat_xml extends qformat_default { return $qo; } - + /** * import description type question * @param array question question array from xml tree @@ -398,7 +398,7 @@ class qformat_xml extends qformat_default { $qo->qtype = ESSAY; // get feedback - $qo->feedback = $this->getpath( $question, array('#','answer',0,'#','feedback',0,'#','text',0,'#'), '', true ); + $qo->feedback = $this->getpath( $question, array('#','answer',0,'#','feedback',0,'#','text',0,'#'), '', true ); // get fraction - tag is deprecated $qo->fraction = $this->getpath( $question, array('@','fraction'), 0 ) / 100; @@ -487,9 +487,9 @@ class qformat_xml extends qformat_default { $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex] = new stdClass(); $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->itemnumber = $datasetitem['#']['number'][0]['#']; //[0]['#']['number'][0]['#'] ; // [0]['numberitems'] ;//['#']['number'][0]['#'];// $datasetitems['#']['number'][0]['#']; $qo->dataset[$qo->datasetindex]->datasetitem[$qo->dataset[$qo->datasetindex]->itemindex]->value = $datasetitem['#']['value'][0]['#'] ;//$datasetitem['#']['value'][0]['#']; - } + } } - + // echo "
loaded qo";print_r($qo);echo "
"; return $qo; @@ -525,7 +525,7 @@ class qformat_xml extends qformat_default { // this converts xml to big nasty data structure // the 0 means keep white space as it is (important for markdown format) // print_r it if you want to see what it looks like! - $xml = xmlize( $text, 0 ); + $xml = xmlize( $text, 0 ); // set up array to hold all our questions $questions = array(); @@ -537,7 +537,7 @@ class qformat_xml extends qformat_default { if ($question_type=='multichoice') { $qo = $this->import_multichoice( $question ); - } + } elseif ($question_type=='truefalse') { $qo = $this->import_truefalse( $question ); } @@ -571,9 +571,9 @@ class qformat_xml extends qformat_default { } else { // try for plugin support - // no default question, as the plugin can call + // no default question, as the plugin can call // import_headers() itself if it wants to - if (!$qo=$this->try_importing_using_qtypes( $question )) { + if (!$qo = $this->try_importing_using_qtypes( $question, null, null, $question_type)) { $notsupported = get_string( 'xmltypeunsupported','quiz',$question_type ); $this->error( $notsupported ); $qo = null; @@ -592,7 +592,7 @@ class qformat_xml extends qformat_default { function export_file_extension() { // override default type so extension is .xml - + return ".xml"; } @@ -672,7 +672,7 @@ class qformat_xml extends qformat_default { } /** - * Convert internal single question code into + * Convert internal single question code into * human readable form * @param int id single question code * @return string single question string @@ -692,7 +692,7 @@ class qformat_xml extends qformat_default { } /** - * generates tags, processing raw text therein + * generates tags, processing raw text therein * @param int ilev the current indent level * @param boolean short stick it on one line * @return string formatted text @@ -714,14 +714,14 @@ class qformat_xml extends qformat_default { return $xml; } - + function xmltidy( $content ) { // can only do this if tidy is installed if (extension_loaded('tidy')) { $config = array( 'input-xml'=>true, 'output-xml'=>true, 'indent'=>true, 'wrap'=>0 ); $tidy = new tidy; $tidy->parseString($content, $config, 'utf8'); - $tidy->cleanRepair(); + $tidy->cleanRepair(); return $tidy->value; } else { @@ -748,11 +748,11 @@ class qformat_xml extends qformat_default { /** * Include an image encoded in base 64 * @param string imagepath The location of the image file - * @return string xml code segment + * @return string xml code segment */ function writeimage( $imagepath ) { global $CFG; - + if (empty($imagepath)) { return ''; } @@ -796,18 +796,18 @@ class qformat_xml extends qformat_default { $expout .= " \n"; $expout .= " \n"; return $expout; - } + } elseif ($question->qtype != MULTIANSWER) { // for all question types except Close $name_text = $this->writetext( $question->name ); $qtformat = $this->get_format($question->questiontextformat); $question_text = $this->writetext( $question->questiontext ); $generalfeedback = $this->writetext( $question->generalfeedback ); - $expout .= " \n"; + $expout .= " \n"; $expout .= " $name_text\n"; $expout .= " \n"; $expout .= $question_text; - $expout .= " \n"; + $expout .= " \n"; $expout .= " {$question->image}\n"; $expout .= $this->writeimage($question->image); $expout .= " \n"; @@ -843,7 +843,7 @@ class qformat_xml extends qformat_default { switch($question->qtype) { case 'category': // not a qtype really - dummy used for category switching - break; + break; case TRUEFALSE: foreach ($question->options->answers as $answer) { $fraction_pc = round( $answer->fraction * 100 ); @@ -851,7 +851,7 @@ class qformat_xml extends qformat_default { $answertext = 'true'; } else { $answertext = 'false'; - } + } $expout .= " \n"; $expout .= $this->writetext($answertext, 3) . "\n"; $expout .= " \n"; @@ -981,7 +981,7 @@ class qformat_xml extends qformat_default { $expout .= " \n"; } $expout .= "\n"; - } + } //The tag $question->export_process has been set so we get all the data items in the database // from the function $QTYPES['calculated']->get_question_options(&$question); // calculatedsimple defaults to calculated @@ -999,7 +999,7 @@ class qformat_xml extends qformat_default { $expout .= " ".$this->writetext($def->distribution)."\n"; $expout .= " ".$this->writetext($def->minimum)."\n"; $expout .= " ".$this->writetext($def->maximum)."\n"; - $expout .= " ".$this->writetext($def->decimals)."\n"; + $expout .= " ".$this->writetext($def->decimals)."\n"; $expout .= " $def->itemcount\n"; if ($def->itemcount > 0 ) { $expout .= " \n"; @@ -1008,18 +1008,18 @@ class qformat_xml extends qformat_default { $expout .= " ".$item->itemnumber."\n"; $expout .= " ".$item->value."\n"; $expout .= " \n"; - } + } $expout .= " \n"; $expout .= " ".$def-> number_of_items."\n"; } $expout .= "\n"; - } - $expout .= "\n"; - } + } + $expout .= "\n"; + } break; default: // try support by optional plugin - if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) { + if (!$data = $this->try_exporting_using_qtypes( $question->qtype, $question )) { echo $OUTPUT->notification( get_string( 'unsupportedexport','qformat_xml',$QTYPES[$question->qtype]->local_name() ) ); } $expout .= $data; diff --git a/question/type/questiontype.php b/question/type/questiontype.php index 837f21545b..e5d8a278fb 100644 --- a/question/type/questiontype.php +++ b/question/type/questiontype.php @@ -1065,7 +1065,7 @@ class default_questiontype { $link = html_link::make($linkurl . '&inpopup=1', $linktext); $link->add_action(new popup_action('click', $link->url, 'editquestion')); $link->title = $stredit; - return $OUTPUT->link($link); + return $OUTPUT->link($link); } } @@ -1121,7 +1121,7 @@ class default_questiontype { $link->add_action(new popup_action('click', $link->url, 'reviewquestion', array('height' => 450, 'width' => 650))); $link->title = $strreviewquestion; $link = $OUTPUT->link($link); - + } else { $link = $st->seq_number; } @@ -1710,6 +1710,88 @@ class default_questiontype { return $state->answer; } +/// IMPORT/EXPORT FUNCTIONS ///////////////// + + /* + * Imports question from the Moodle XML format + * + * Imports question using information from extra_question_fields function + * If some of you fields contains id's you'll need to reimplement this + */ + function import_from_xml($data, $question, $format, $extra=null) { + $question_type = $data['@']['type']; + if ($question_type != $this->name()) { + return false; + } + + $extraquestionfields = $this->extra_question_fields(); + if (!is_array($extraquestionfields)) { + return false; + } + + //omit table name + array_shift($extraquestionfields); + $qo = $format->import_headers($data); + $qo->qtype = $question_type; + + foreach ($extraquestionfields as $field) { + $qo->$field = $format->getpath($data, array('#',$field,0,'#'), $qo->$field); + } + + // run through the answers + $answers = $data['#']['answer']; + $a_count = 0; + $extraasnwersfields = $this->extra_answer_fields(); + if (is_array($extraasnwersfields)) { + //TODO import the answers, with any extra data. + } else { + foreach ($answers as $answer) { + $ans = $format->import_answer($answer); + $qo->answer[$a_count] = $ans->answer; + $qo->fraction[$a_count] = $ans->fraction; + $qo->feedback[$a_count] = $ans->feedback; + ++$a_count; + } + } + return $qo; + } + + /* + * Export question to the Moodle XML format + * + * Export question using information from extra_question_fields function + * If some of you fields contains id's you'll need to reimplement this + */ + function export_to_xml($question, $format, $extra=null) { + $extraquestionfields = $this->extra_question_fields(); + if (!is_array($extraquestionfields)) { + return false; + } + + //omit table name + array_shift($extraquestionfields); + $expout=''; + foreach ($extraquestionfields as $field) { + $expout .= " <$field>{$question->options->$field}\n"; + } + + $extraasnwersfields = $this->extra_answer_fields(); + if (is_array($extraasnwersfields)) { + //TODO export answers with any extra data + } else { + foreach ($question->options->answers as $answer) { + $percent = 100 * $answer->fraction; + $expout .= " \n"; + $expout .= $format->writetext($answer->answer, 3, false); + $expout .= " \n"; + $expout .= $format->writetext($answer->feedback, 4, false); + $expout .= " \n"; + $expout .= " \n"; + } + } + return $expout; + } + /** * Abstract function implemented by each question type. It runs all the code * required to set up and save a question of any type for testing purposes. -- 2.39.5