]> git.mjollnir.org Git - moodle.git/commitdiff
question types: MDL-20157 export_to_xml and import_from_xml functions for question...
authortjhunt <tjhunt>
Wed, 30 Sep 2009 13:38:36 +0000 (13:38 +0000)
committertjhunt <tjhunt>
Wed, 30 Sep 2009 13:38:36 +0000 (13:38 +0000)
Thanks to Oleg Sychev for the implementation.

question/format.php
question/format/xml/format.php
question/type/questiontype.php

index 1356844ca2e8d4212f812a5333696b29d8fd5892..74d40c8aa524fad000750e86e9e033f46d94d0ca 100644 (file)
@@ -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) {
index 582b869c6ac8b7db96674af3a9fcb59043987a0c..1924328d5d7e7e7efa2e59180f28849507a302e1 100755 (executable)
@@ -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 - <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 "<pre>loaded qo";print_r($qo);echo "</pre>";
 
         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 <text></text> tags, processing raw text therein 
+     * generates <text></text> 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 .= "    </category>\n";
             $expout .= "  </question>\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 .= "  <question type=\"$question_type\">\n";   
+            $expout .= "  <question type=\"$question_type\">\n";
             $expout .= "    <name>$name_text</name>\n";
             $expout .= "    <questiontext format=\"$qtformat\">\n";
             $expout .= $question_text;
-            $expout .= "    </questiontext>\n";   
+            $expout .= "    </questiontext>\n";
             $expout .= "    <image>{$question->image}</image>\n";
             $expout .= $this->writeimage($question->image);
             $expout .= "    <generalfeedback>\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 .= "    <answer fraction=\"$fraction_pc\">\n";
                 $expout .= $this->writetext($answertext, 3) . "\n";
                 $expout .= "      <feedback>\n";
@@ -981,7 +981,7 @@ class qformat_xml extends qformat_default {
                     $expout .= "  </unit>\n";
                 }
                 $expout .= "</units>\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 .= "    <distribution>".$this->writetext($def->distribution)."</distribution>\n";
                     $expout .= "    <minimum>".$this->writetext($def->minimum)."</minimum>\n";
                     $expout .= "    <maximum>".$this->writetext($def->maximum)."</maximum>\n";
-                    $expout .= "    <decimals>".$this->writetext($def->decimals)."</decimals>\n";               
+                    $expout .= "    <decimals>".$this->writetext($def->decimals)."</decimals>\n";
                     $expout .= "    <itemcount>$def->itemcount</itemcount>\n";
                     if ($def->itemcount > 0 ) {
                         $expout .= "    <dataset_items>\n";
@@ -1008,18 +1008,18 @@ class qformat_xml extends qformat_default {
                               $expout .= "           <number>".$item->itemnumber."</number>\n";
                               $expout .= "           <value>".$item->value."</value>\n";
                               $expout .= "        </dataset_item>\n";
-                        }        
+                        }
                         $expout .= "    </dataset_items>\n";
                         $expout .= "    <number_of_items>".$def-> number_of_items."</number_of_items>\n";
                      }
                     $expout .= "</dataset_definition>\n";
-                } 
-                $expout .= "</dataset_definitions>\n";                                                                                
-            }                      
+                }
+                $expout .= "</dataset_definitions>\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;
index 837f21545b7064c260991bd00996c9fa120e5f84..e5d8a278fb06abd2b457dcf98d6fb052f5ad64c5 100644 (file)
@@ -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}</$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 .= "    <answer fraction=\"$percent\">\n";
+                $expout .= $format->writetext($answer->answer, 3, false);
+                $expout .= "      <feedback>\n";
+                $expout .= $format->writetext($answer->feedback, 4, false);
+                $expout .= "      </feedback>\n";
+                $expout .= "    </answer>\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.