MDL-25492 Blackboard V6+ question import is broken.
authorJean-Michel Vedrine <vedrine@vedrine.org>
Sat, 11 Aug 2012 19:00:54 +0000 (21:00 +0200)
committerJean-Michel Vedrine <vedrine@vedrine.org>
Fri, 24 Aug 2012 11:52:05 +0000 (13:52 +0200)
14 files changed:
question/format.php
question/format/blackboard/format.php
question/format/blackboard_six/format.php
question/format/blackboard_six/formatbase.php [new file with mode: 0644]
question/format/blackboard_six/formatpool.php [new file with mode: 0644]
question/format/blackboard_six/formatqti.php [new file with mode: 0644]
question/format/blackboard_six/lang/en/qformat_blackboard_six.php
question/format/blackboard_six/tests/blackboardformatpool_test.php [new file with mode: 0644]
question/format/blackboard_six/tests/blackboardsixformatqti_test.php [new file with mode: 0644]
question/format/blackboard_six/tests/fixtures/sample_blackboard_pool.dat [new file with mode: 0644]
question/format/blackboard_six/tests/fixtures/sample_blackboard_qti.dat [new file with mode: 0644]
question/format/blackboard_six/version.php
question/format/examview/format.php
question/format/gift/format.php

index a0e9df4..dd02cfc 100644 (file)
@@ -408,20 +408,44 @@ class qformat_default {
             $question->timecreated = time();
             $question->modifiedby = $USER->id;
             $question->timemodified = time();
+            $fileoptions = array(
+                    'subdirs' => false,
+                    'maxfiles' => -1,
+                    'maxbytes' => 0,
+                );
+            if (is_array($question->questiontext)) {
+                // Importing images from draftfile.
+                $questiontext = $question->questiontext;
+                $question->questiontext = $questiontext['text'];
+            }
+            if (is_array($question->generalfeedback)) {
+                $generalfeedback = $question->generalfeedback;
+                $question->generalfeedback = $generalfeedback['text'];
+            }
 
             $question->id = $DB->insert_record('question', $question);
-            if (isset($question->questiontextfiles)) {
+
+            if (!empty($questiontext['itemid'])) {
+                $question->questiontext = file_save_draft_area_files($questiontext['itemid'],
+                        $this->importcontext->id, 'question', 'questiontext', $question->id,
+                        $fileoptions, $question->questiontext);
+            } else if (isset($question->questiontextfiles)) {
                 foreach ($question->questiontextfiles as $file) {
                     question_bank::get_qtype($question->qtype)->import_file(
                             $this->importcontext, 'question', 'questiontext', $question->id, $file);
                 }
             }
-            if (isset($question->generalfeedbackfiles)) {
+            if (!empty($generalfeedback['itemid'])) {
+                $question->generalfeedback = file_save_draft_area_files($generalfeedback['itemid'],
+                        $this->importcontext->id, 'question', 'generalfeedback', $question->id,
+                        $fileoptions, $question->generalfeedback);
+            } else if (isset($question->generalfeedbackfiles)) {
                 foreach ($question->generalfeedbackfiles as $file) {
                     question_bank::get_qtype($question->qtype)->import_file(
                             $this->importcontext, 'question', 'generalfeedback', $question->id, $file);
                 }
             }
+            $DB->update_record('question', $question);
 
             $this->questionids[] = $question->id;
 
@@ -636,6 +660,24 @@ class qformat_default {
         return $question;
     }
 
+    /**
+     * Add a blank combined feedback to a question object.
+     * @param object question
+     * @return object question
+     */
+    protected function add_blank_combined_feedback($question) {
+        $question->correctfeedback['text'] = '';
+        $question->correctfeedback['format'] = $question->questiontextformat;
+        $question->correctfeedback['files'] = array();
+        $question->partiallycorrectfeedback['text'] = '';
+        $question->partiallycorrectfeedback['format'] = $question->questiontextformat;
+        $question->partiallycorrectfeedback['files'] = array();
+        $question->incorrectfeedback['text'] = '';
+        $question->incorrectfeedback['format'] = $question->questiontextformat;
+        $question->incorrectfeedback['files'] = array();
+        return $question;
+    }
+
     /**
      * Given the data known to define a question in
      * this format, this function converts it into a question
@@ -901,6 +943,28 @@ class qformat_default {
 
 class qformat_based_on_xml extends qformat_default {
 
+    /**
+     * A lot of imported files contain unwanted entities.
+     * This method tries to clean up all known problems.
+     * @param string str string to correct
+     * @return string the corrected string
+     */
+    public function cleaninput($str) {
+
+        $html_code_list = array(
+            "&#039;" => "'",
+            "&#8217;" => "'",
+            "&#8220;" => "\"",
+            "&#8221;" => "\"",
+            "&#8211;" => "-",
+            "&#8212;" => "-",
+        );
+        $str = strtr($str, $html_code_list);
+        // Use textlib entities_to_utf8 function to convert only numerical entities.
+        $str = textlib::entities_to_utf8($str, false);
+        return $str;
+    }
+
     /**
      * Return the array moodle is expecting
      * for an HTML text. No processing is done on $text.
index 88e0130..1e7fa0d 100644 (file)
@@ -59,33 +59,6 @@ class qformat_blackboard extends qformat_based_on_xml {
         return mimeinfo('type', '.dat');
     }
 
-    /**
-     * Some softwares put entities in exported files.
-     * This method try to clean up known problems.
-     * @param string str string to correct
-     * @return string the corrected string
-     */
-    public function cleaninput($str) {
-        if (!$this->ishtml) {
-            return $str;
-        }
-        $html_code_list = array(
-            "&#039;" => "'",
-            "&#8217;" => "'",
-            "&#091;" => "[",
-            "&#8220;" => "\"",
-            "&#8221;" => "\"",
-            "&#093;" => "]",
-            "&#039;" => "'",
-            "&#8211;" => "-",
-            "&#8212;" => "-",
-        );
-        $str = strtr($str, $html_code_list);
-        // Use textlib entities_to_utf8 function to convert only numerical entities.
-        $str = textlib::entities_to_utf8($str, false);
-        return $str;
-    }
-
     /**
      * Parse the array of lines into an array of questions
      * this *could* burn memory - but it won't happen that much
index 047bfa9..32652d1 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Blackboard 6.0 question importer.
+ * Blackboard V5 and V6 question importer.
  *
- * @package    qformat
- * @subpackage blackboard_six
+ * @package    qformat_blackboard_six
  * @copyright  2005 Michael Penney
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-
 defined('MOODLE_INTERNAL') || die();
 
-require_once ($CFG->libdir . '/xmlize.php');
-
-
-/**
- * Blackboard 6.0 question importer.
- *
- * @copyright  2005 Michael Penney
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class qformat_blackboard_six extends qformat_default {
-    function provide_import() {
-        return true;
-    }
-
-    public function can_import_file($file) {
-        $mimetypes = array(
-            mimeinfo('type', '.dat'),
-            mimeinfo('type', '.zip')
-        );
-        return in_array($file->get_mimetype(), $mimetypes);
-    }
-
-
-    //Function to check and create the needed dir to unzip file to
-    function check_and_create_import_dir($unique_code) {
-
-        global $CFG;
-
-        $status = $this->check_dir_exists($CFG->tempdir."",true);
-        if ($status) {
-            $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import",true);
-        }
-        if ($status) {
-            $status = $this->check_dir_exists($CFG->tempdir."/bbquiz_import/".$unique_code,true);
-        }
-
-        return $status;
-    }
-
-    function clean_temp_dir($dir='') {
+require_once($CFG->libdir . '/xmlize.php');
+require_once($CFG->dirroot . '/question/format/blackboard_six/formatbase.php');
+require_once($CFG->dirroot . '/question/format/blackboard_six/formatqti.php');
+require_once($CFG->dirroot . '/question/format/blackboard_six/formatpool.php');
+
+class qformat_blackboard_six extends qformat_blackboard_six_base {
+    /** @var int Blackboard assessment qti files were always imported by the blackboard_six plugin. */
+    const FILETYPE_QTI = 1;
+    /** @var int Blackboard question pool files were previously handled by the blackboard plugin. */
+    const FILETYPE_POOL = 2;
+    /** @var int type of file being imported, one of the constants FILETYPE_QTI or FILETYPE_POOL. */
+    public $filetype;
+
+    public function get_filecontent($path) {
+        $fullpath = $this->tempdir . '/' . $path;
+        if (is_file($fullpath) && is_readable($fullpath)) {
+            return file_get_contents($fullpath);
+        }
+        return false;
+    }
+
+    /**
+     * Set the file type being imported
+     * @param int $type the imported file's type
+     */
+    public function set_filetype($type) {
+        $this->filetype = $type;
+    }
+
+    /**
+     * Return content of all files containing questions,
+     * as an array one element for each file found,
+     * For each file, the corresponding element is an array of lines.
+     * @param string filename name of file
+     * @return mixed contents array or false on failure
+     */
+    public function readdata($filename) {
         global $CFG;
 
-        // for now we will just say everything happened okay note
-        // that a mess may be piling up in $CFG->tempdir/bbquiz_import
-        // TODO return true at top of the function renders all the following code useless
-        return true;
-
-        if ($dir == '') {
-            $dir = $this->temp_dir;
-        }
-        $slash = "/";
-
-        // Create arrays to store files and directories
-        $dir_files      = array();
-        $dir_subdirs    = array();
-
-        // Make sure we can delete it
-        chmod($dir, $CFG->directorypermissions);
-
-        if ((($handle = opendir($dir))) == FALSE) {
-            // The directory could not be opened
-            return false;
-        }
-
-        // Loop through all directory entries, and construct two temporary arrays containing files and sub directories
-        while(false !== ($entry = readdir($handle))) {
-            if (is_dir($dir. $slash .$entry) && $entry != ".." && $entry != ".") {
-                $dir_subdirs[] = $dir. $slash .$entry;
-            }
-            else if ($entry != ".." && $entry != ".") {
-                $dir_files[] = $dir. $slash .$entry;
-            }
-        }
-
-        // Delete all files in the curent directory return false and halt if a file cannot be removed
-        $countdir_files = count($dir_files);
-        for($i=0; $i<$countdir_files; $i++) {
-            chmod($dir_files[$i], $CFG->directorypermissions);
-            if (((unlink($dir_files[$i]))) == FALSE) {
+        // Find if we are importing a .dat file.
+        if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) == 'dat') {
+            if (!is_readable($filename)) {
+                $this->error(get_string('filenotreadable', 'error'));
                 return false;
             }
-        }
-
-        // Empty sub directories and then remove the directory
-        $countdir_subdirs = count($dir_subdirs);
-        for($i=0; $i<$countdir_subdirs; $i++) {
-            chmod($dir_subdirs[$i], $CFG->directorypermissions);
-            if ($this->clean_temp_dir($dir_subdirs[$i]) == FALSE) {
-                return false;
+            // As we are not importing a .zip file,
+            // there is no imsmanifest, and it is not possible
+            // to parse it to find the file type.
+            // So we need to guess the file type by looking at the content.
+            // For now we will do that searching for a required tag.
+            // This is certainly not bullet-proof but works for all usual files.
+            $text = file_get_contents($filename);
+            if (strpos($text, '<questestinterop>')) {
+                $this->set_filetype(self::FILETYPE_QTI);
             }
-            else {
-                if (rmdir($dir_subdirs[$i]) == FALSE) {
-                return false;
-                }
+            if (strpos($text, '<POOL>')) {
+                $this->set_filetype(self::FILETYPE_POOL);
             }
-        }
+            // In all other cases we are not able to handle this question file.
 
-        // Close directory
-        closedir($handle);
-        if (rmdir($this->temp_dir) == FALSE) {
-            return false;
+            // Readquestions is now expecting an array of strings.
+            return array($text);
         }
-        // Success, every thing is gone return true
-        return true;
-    }
-
-    //Function to check if a directory exists and, optionally, create it
-    function check_dir_exists($dir,$create=false) {
-
-        global $CFG;
-
-        $status = true;
-        if(!is_dir($dir)) {
-            if (!$create) {
-                $status = false;
-            } else {
-                umask(0000);
-                $status = mkdir ($dir,$CFG->directorypermissions);
+        // We are importing a zip file.
+        // Create name for temporary directory.
+        $unique_code = time();
+        $this->tempdir = make_temp_directory('bbquiz_import/' . $unique_code);
+        if (is_readable($filename)) {
+            if (!copy($filename, $this->tempdir . '/bboard.zip')) {
+                $this->error(get_string('cannotcopybackup', 'question'));
+                fulldelete($this->tempdir);
+                return false;
             }
-        }
-        return $status;
-    }
+            if (unzip_file($this->tempdir . '/bboard.zip', '', false)) {
+                $dom = new DomDocument();
 
-    function importpostprocess() {
-    /// Does any post-processing that may be desired
-    /// Argument is a simple array of question ids that
-    /// have just been added.
-
-        // need to clean up temporary directory
-        return $this->clean_temp_dir();
-    }
-
-    function copy_file_to_course($filename) {
-        global $CFG, $COURSE;
-        $filename = str_replace('\\','/',$filename);
-        $fullpath = $this->temp_dir.'/res00001/'.$filename;
-        $basename = basename($filename);
-
-        $copy_to = $CFG->dataroot.'/'.$COURSE->id.'/bb_import';
-
-        if ($this->check_dir_exists($copy_to,true)) {
-            if(is_readable($fullpath)) {
-                $copy_to.= '/'.$basename;
-                if (!copy($fullpath, $copy_to)) {
+                if (!$dom->load($this->tempdir . '/imsmanifest.xml')) {
+                    $this->error(get_string('errormanifest', 'qformat_blackboard_six'));
+                    fulldelete($this->tempdir);
                     return false;
                 }
-                else {
-                    return $copy_to;
-                }
-            }
-        }
-        else {
-            return false;
-        }
-    }
 
-    function readdata($filename) {
-    /// Returns complete file with an array, one item per line
-        global $CFG;
+                $xpath = new DOMXPath($dom);
 
-        // if the extension is .dat we just return that,
-        // if .zip we unzip the file and get the data
-        $ext = substr($this->realfilename, strpos($this->realfilename,'.'), strlen($this->realfilename)-1);
-        if ($ext=='.dat') {
-            if (!is_readable($filename)) {
-                print_error('filenotreadable', 'error');
-            }
-            return file($filename);
-        }
+                // We starts from the root element.
+                $query = '//resources/resource';
+                $this->filebase = $this->tempdir;
+                $q_file = array();
 
-        $unique_code = time();
-        $temp_dir = $CFG->tempdir."/bbquiz_import/".$unique_code;
-        $this->temp_dir = $temp_dir;
-        if ($this->check_and_create_import_dir($unique_code)) {
-            if(is_readable($filename)) {
-                if (!copy($filename, "$temp_dir/bboard.zip")) {
-                    print_error('cannotcopybackup', 'question');
-                }
-                if(unzip_file("$temp_dir/bboard.zip", '', false)) {
-                    // assuming that the information is in res0001.dat
-                    // after looking at 6 examples this was always the case
-                    $q_file = "$temp_dir/res00001.dat";
-                    if (is_file($q_file)) {
-                        if (is_readable($q_file)) {
-                            $filearray = file($q_file);
-                            /// Check for Macintosh OS line returns (ie file on one line), and fix
-                            if (preg_match("~\r~", $filearray[0]) AND !preg_match("~\n~", $filearray[0])) {
-                                return explode("\r", $filearray[0]);
-                            } else {
-                                return $filearray;
-                            }
+                $examfiles = $xpath->query($query);
+                foreach ($examfiles as $examfile) {
+                    if ($examfile->getAttribute('type') == 'assessment/x-bb-qti-test'
+                            || $examfile->getAttribute('type') == 'assessment/x-bb-qti-pool') {
+
+                        if ($content = $this->get_filecontent($examfile->getAttribute('bb:file'))) {
+                            $this->set_filetype(self::FILETYPE_QTI);
+                            $q_file[] = $content;
                         }
                     }
-                    else {
-                        print_error('cannotfindquestionfile', 'questioni');
-                    }
-                }
-                else {
-                    print "filename: $filename<br />tempdir: $temp_dir <br />";
-                    print_error('cannotunzip', 'question');
-                }
-            }
-            else {
-                print_error('cannotreaduploadfile');
-            }
-        }
-        else {
-            print_error('cannotcreatetempdir');
-        }
-    }
-
-    function save_question_options($question) {
-        return true;
-    }
-
-
-
-  protected function readquestions($lines) {
-    /// Parses an array of lines into an array of questions,
-    /// where each item is a question object as defined by
-    /// readquestion().
-
-    $text = implode($lines, " ");
-    $xml = xmlize($text, 0);
-
-    $raw_questions = $xml['questestinterop']['#']['assessment'][0]['#']['section'][0]['#']['item'];
-    $questions = array();
-
-    foreach($raw_questions as $quest) {
-        $question = $this->create_raw_question($quest);
-
-        switch($question->qtype) {
-            case "Matching":
-                $this->process_matching($question, $questions);
-                break;
-            case "Multiple Choice":
-                $this->process_mc($question, $questions);
-                break;
-            case "Essay":
-                $this->process_essay($question, $questions);
-                break;
-            case "Multiple Answer":
-                $this->process_ma($question, $questions);
-                break;
-            case "True/False":
-                $this->process_tf($question, $questions);
-                break;
-            case 'Fill in the Blank':
-                $this->process_fblank($question, $questions);
-                break;
-            case 'Short Response':
-                $this->process_essay($question, $questions);
-                break;
-            default:
-                print "Unknown or unhandled question type: \"$question->qtype\"<br />";
-                break;
-        }
-
-    }
-    return $questions;
-  }
-
-
-// creates a cleaner object to deal with for processing into moodle
-// the object created is NOT a moodle question object
-function create_raw_question($quest) {
-
-    $question = new stdClass();
-    $question->qtype = $quest['#']['itemmetadata'][0]['#']['bbmd_questiontype'][0]['#'];
-    $question->id = $quest['#']['itemmetadata'][0]['#']['bbmd_asi_object_id'][0]['#'];
-    $presentation->blocks = $quest['#']['presentation'][0]['#']['flow'][0]['#']['flow'];
-
-    foreach($presentation->blocks as $pblock) {
-
-        $block = NULL;
-        $block->type = $pblock['@']['class'];
-
-        switch($block->type) {
-            case 'QUESTION_BLOCK':
-                $sub_blocks = $pblock['#']['flow'];
-                foreach($sub_blocks as $sblock) {
-                    //echo "Calling process_block from line 263<br>";
-                    $this->process_block($sblock, $block);
-                }
-                break;
-
-            case 'RESPONSE_BLOCK':
-                $choices = NULL;
-                switch($question->qtype) {
-                    case 'Matching':
-                        $bb_subquestions = $pblock['#']['flow'];
-                        $sub_questions = array();
-                        foreach($bb_subquestions as $bb_subquestion) {
-                            $sub_question = NULL;
-                            $sub_question->ident = $bb_subquestion['#']['response_lid'][0]['@']['ident'];
-                            $this->process_block($bb_subquestion['#']['flow'][0], $sub_question);
-                            $bb_choices = $bb_subquestion['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
-                            $choices = array();
-                            $this->process_choices($bb_choices, $choices);
-                            $sub_question->choices = $choices;
-                            if (!isset($block->subquestions)) {
-                                $block->subquestions = array();
-                            }
-                            $block->subquestions[] = $sub_question;
+                    if ($examfile->getAttribute('type') == 'assessment/x-bb-pool') {
+                        if ($examfile->getAttribute('baseurl')) {
+                            $this->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
                         }
-                        break;
-                    case 'Multiple Answer':
-                        $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
-                        $choices = array();
-                        $this->process_choices($bb_choices, $choices);
-                        $block->choices = $choices;
-                        break;
-                    case 'Essay':
-                        // Doesn't apply since the user responds with text input
-                        break;
-                    case 'Multiple Choice':
-                        $mc_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'];
-                            foreach($mc_choices as $mc_choice) {
-                            $choices = NULL;
-                            $choices = $this->process_block($mc_choice, $choices);
-                            $block->choices[] = $choices;
+                        if ($content = $this->get_filecontent($examfile->getAttribute('file'))) {
+                            $this->set_filetype(self::FILETYPE_POOL);
+                            $q_file[] = $content;
                         }
-                        break;
-                    case 'Short Response':
-                        // do nothing?
-                        break;
-                    case 'Fill in the Blank':
-                        // do nothing?
-                        break;
-                    default:
-                        $bb_choices = $pblock['#']['response_lid'][0]['#']['render_choice'][0]['#']['flow_label'][0]['#']['response_label'];
-                        $choices = array();
-                        $this->process_choices($bb_choices, $choices);
-                        $block->choices = $choices;
-                }
-                break;
-            case 'RIGHT_MATCH_BLOCK':
-                $matching_answerset = $pblock['#']['flow'];
-
-                $answerset = array();
-                foreach($matching_answerset as $answer) {
-                    // $answerset[] = $this->process_block($answer, $bb_answer);
-                    $bb_answer = null;
-                    $bb_answer->text = $answer['#']['flow'][0]['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
-                    $answerset[] = $bb_answer;
-                }
-                $block->matching_answerset = $answerset;
-                break;
-            default:
-                print "UNHANDLED PRESENTATION BLOCK";
-                break;
-        }
-        $question->{$block->type} = $block;
-    }
-
-    // determine response processing
-    // there is a section called 'outcomes' that I don't know what to do with
-    $resprocessing = $quest['#']['resprocessing'];
-    $respconditions = $resprocessing[0]['#']['respcondition'];
-    $reponses = array();
-    if ($question->qtype == 'Matching') {
-        $this->process_matching_responses($respconditions, $responses);
-    }
-    else {
-        $this->process_responses($respconditions, $responses);
-    }
-    $question->responses = $responses;
-    $feedbackset = $quest['#']['itemfeedback'];
-    $feedbacks = array();
-    $this->process_feedback($feedbackset, $feedbacks);
-    $question->feedback = $feedbacks;
-    return $question;
-}
-
-function process_block($cur_block, &$block) {
-    global $COURSE, $CFG;
-
-    $cur_type = $cur_block['@']['class'];
-    switch($cur_type) {
-        case 'FORMATTED_TEXT_BLOCK':
-            $block->text = $this->strip_applet_tags_get_mathml($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#']);
-            break;
-        case 'FILE_BLOCK':
-            //revisit this to make sure it is working correctly
-            // Commented out ['matapplication']..., etc. because I
-            // noticed that when I imported a new Blackboard 6 file
-            // and printed out the block, the tree did not extend past ['material'][0]['#'] - CT 8/3/06
-            $block->file = $cur_block['#']['material'][0]['#'];//['matapplication'][0]['@']['uri'];
-            if ($block->file != '') {
-                // if we have a file copy it to the course dir and adjust its name to be visible over the web.
-                $block->file = $this->copy_file_to_course($block->file);
-                $block->file = $CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($block->file);
-            }
-            break;
-        case 'Block':
-            if (isset($cur_block['#']['material'][0]['#']['mattext'][0]['#'])) {
-            $block->text = $cur_block['#']['material'][0]['#']['mattext'][0]['#'];
-            }
-            else if (isset($cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'])) {
-                $block->text = $cur_block['#']['material'][0]['#']['mat_extension'][0]['#']['mat_formattedtext'][0]['#'];
-            }
-            else if (isset($cur_block['#']['response_label'])) {
-                // this is a response label block
-                $sub_blocks = $cur_block['#']['response_label'][0];
-                if(!isset($block->ident)) {
-                    if(isset($sub_blocks['@']['ident'])) {
-                        $block->ident = $sub_blocks['@']['ident'];
                     }
                 }
-                foreach($sub_blocks['#']['flow_mat'] as $sub_block) {
-                    $this->process_block($sub_block, $block);
-                }
-            }
-            else {
-                if (isset($cur_block['#']['flow_mat']) || isset($cur_block['#']['flow'])) {
-                    if (isset($cur_block['#']['flow_mat'])) {
-                        $sub_blocks = $cur_block['#']['flow_mat'];
-                    }
-                    elseif (isset($cur_block['#']['flow'])) {
-                        $sub_blocks = $cur_block['#']['flow'];
-                    }
-                   foreach ($sub_blocks as $sblock) {
-                        // this will recursively grab the sub blocks which should be of one of the other types
-                        $this->process_block($sblock, $block);
-                    }
-                }
-            }
-            break;
-        case 'LINK_BLOCK':
-            // not sure how this should be included
-            if (!empty($cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'])) {
-                $block->link = $cur_block['#']['material'][0]['#']['mattext'][0]['@']['uri'];
-            }
-            else {
-               $block->link = '';
-            }
-            break;
-    }
-    return $block;
-}
 
-function process_choices($bb_choices, &$choices) {
-    foreach($bb_choices as $choice) {
-            if (isset($choice['@']['ident'])) {
-            $cur_choice = $choice['@']['ident'];
-        }
-        else { //for multiple answer
-            $cur_choice = $choice['#']['response_label'][0];//['@']['ident'];
-        }
-        if (isset($choice['#']['flow_mat'][0])) { //for multiple answer
-            $cur_block = $choice['#']['flow_mat'][0];
-            // Reset $cur_choice to NULL because process_block is expecting an object
-            // for the second argument and not a string, which is what is was set as
-            // originally - CT 8/7/06
-            $cur_choice = null;
-            $this->process_block($cur_block, $cur_choice);
-        }
-        elseif (isset($choice['#']['response_label'])) {
-            // Reset $cur_choice to NULL because process_block is expecting an object
-            // for the second argument and not a string, which is what is was set as
-            // originally - CT 8/7/06
-            $cur_choice = null;
-            $this->process_block($choice, $cur_choice);
-        }
-        $choices[] = $cur_choice;
-    }
-}
-
-function process_matching_responses($bb_responses, &$responses) {
-    foreach($bb_responses as $bb_response) {
-        $response = NULL;
-        if (isset($bb_response['#']['conditionvar'][0]['#']['varequal'])) {
-            $response->correct = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['#'];
-            $response->ident = $bb_response['#']['conditionvar'][0]['#']['varequal'][0]['@']['respident'];
-        }
-        else {
-            $response->correct =  'Broken Question?';
-            $response->ident = 'Broken Question?';
-        }
-        $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
-        $responses[] = $response;
-    }
-}
-
-function process_responses($bb_responses, &$responses) {
-    foreach($bb_responses as $bb_response) {
-        //Added this line to instantiate $response.
-        // Without instantiating the $response variable, the same object
-        // gets added to the array
-        $response = new stdClass();
-        if (isset($bb_response['@']['title'])) {
-                $response->title = $bb_response['@']['title'];
-            }
-            else {
-                $reponse->title = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
-            }
-            $reponse->ident = array();
-            if (isset($bb_response['#']['conditionvar'][0]['#'])){//['varequal'][0]['#'])) {
-                $response->ident[0] = $bb_response['#']['conditionvar'][0]['#'];//['varequal'][0]['#'];
-            }
-            else if (isset($bb_response['#']['conditionvar'][0]['#']['other'][0]['#'])) {
-                $response->ident[0] = $bb_response['#']['conditionvar'][0]['#']['other'][0]['#'];
-            }
-
-            if (isset($bb_response['#']['conditionvar'][0]['#']['and'])){//[0]['#'])) {
-                $responseset = $bb_response['#']['conditionvar'][0]['#']['and'];//[0]['#']['varequal'];
-                foreach($responseset as $rs) {
-                    $response->ident[] = $rs['#'];
-                    if(!isset($response->feedback) and isset( $rs['@'] ) ) {
-                        $response->feedback = $rs['@']['respident'];
-                    }
-                }
-            }
-            else {
-                $response->feedback = $bb_response['#']['displayfeedback'][0]['@']['linkrefid'];
-            }
-
-            // determine what point value to give response
-            if (isset($bb_response['#']['setvar'])) {
-                switch ($bb_response['#']['setvar'][0]['#']) {
-                    case "SCORE.max":
-                        $response->fraction = 1;
-                        break;
-                    default:
-                        // I have only seen this being 0 or unset
-                        // there are probably fractional values of SCORE.max, but I'm not sure what they look like
-                        $response->fraction = 0;
-                        break;
-                }
-            }
-            else {
-               // just going to assume this is the case this is probably not correct.
-               $response->fraction = 0;
-            }
-
-            $responses[] = $response;
-        }
-}
-
-function process_feedback($feedbackset, &$feedbacks) {
-    foreach($feedbackset as $bb_feedback) {
-        // Added line $feedback=null so that $feedback does not get reused in the loop
-        // and added the the $feedbacks[] array multiple times
-        $feedback = null;
-        $feedback->ident = $bb_feedback['@']['ident'];
-        if (isset($bb_feedback['#']['flow_mat'][0])) {
-            $this->process_block($bb_feedback['#']['flow_mat'][0], $feedback);
-        }
-        elseif (isset($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0])) {
-            $this->process_block($bb_feedback['#']['solution'][0]['#']['solutionmaterial'][0]['#']['flow_mat'][0], $feedback);
-        }
-        $feedbacks[] = $feedback;
-    }
-}
-
-/**
- * Create common parts of question
- */
-function process_common( $quest ) {
-    $question = $this->defaultquestion();
-    $question->questiontext = $quest->QUESTION_BLOCK->text;
-    $question->name = shorten_text( $quest->id, 250 );
-
-    return $question;
-}
-
-//----------------------------------------
-// Process True / False Questions
-//----------------------------------------
-function process_tf($quest, &$questions) {
-    $question = $this->process_common( $quest );
-
-    $question->qtype = TRUEFALSE;
-    $question->single = 1; // Only one answer is allowed
-    // 0th [response] is the correct answer.
-    $responses = $quest->responses;
-    $correctresponse = $responses[0]->ident[0]['varequal'][0]['#'];
-    if ($correctresponse != 'false') {
-        $correct = true;
-    }
-    else {
-        $correct = false;
-    }
-
-    foreach($quest->feedback as $fb) {
-        $fback->{$fb->ident} = $fb->text;
-    }
-
-    if ($correct) {  // true is correct
-        $question->answer = 1;
-        $question->feedbacktrue = $fback->correct;
-        $question->feedbackfalse = $fback->incorrect;
-    } else {  // false is correct
-        $question->answer = 0;
-        $question->feedbacktrue = $fback->incorrect;
-        $question->feedbackfalse = $fback->correct;
-    }
-    $question->correctanswer = $question->answer;
-    $questions[] = $question;
-}
-
-
-//----------------------------------------
-// Process Fill in the Blank
-//----------------------------------------
-function process_fblank($quest, &$questions) {
-    $question = $this->process_common( $quest );
-    $question->qtype = SHORTANSWER;
-    $question->single = 1;
-
-    $answers = array();
-    $fractions = array();
-    $feedbacks = array();
-
-    // extract the feedback
-    $feedback = array();
-    foreach($quest->feedback as $fback) {
-        if (isset($fback->ident)) {
-            if ($fback->ident == 'correct' || $fback->ident == 'incorrect') {
-                $feedback[$fback->ident] = $fback->text;
-            }
-        }
-    }
-
-    foreach($quest->responses as $response) {
-        if(isset($response->title)) {
-            if (isset($response->ident[0]['varequal'][0]['#'])) {
-                //for BB Fill in the Blank, only interested in correct answers
-                if ($response->feedback = 'correct') {
-                    $answers[] = $response->ident[0]['varequal'][0]['#'];
-                    $fractions[] = 1;
-                    if (isset($feedback['correct'])) {
-                        $feedbacks[] = $feedback['correct'];
-                    }
-                    else {
-                        $feedbacks[] = '';
-                    }
+                if ($q_file) {
+                    return $q_file;
+                } else {
+                    $this->error(get_string('cannotfindquestionfile', 'question'));
+                    fulldelete($this->tempdir);
                 }
-            }
-
-        }
-    }
-
-    //Adding catchall to so that students can see feedback for incorrect answers when they enter something the
-    //instructor did not enter
-    $answers[] = '*';
-    $fractions[] = 0;
-    if (isset($feedback['incorrect'])) {
-        $feedbacks[] = $feedback['incorrect'];
-    }
-    else {
-        $feedbacks[] = '';
-    }
-
-    $question->answer = $answers;
-    $question->fraction = $fractions;
-    $question->feedback = $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of
-
-    if (!empty($question)) {
-        $questions[] = $question;
-    }
-
-}
-
-//----------------------------------------
-// Process Multiple Choice Questions
-//----------------------------------------
-function process_mc($quest, &$questions) {
-    $question = $this->process_common( $quest );
-    $question->qtype = MULTICHOICE;
-    $question->single = 1;
-
-    $feedback = array();
-    foreach($quest->feedback as $fback) {
-        $feedback[$fback->ident] = $fback->text;
-    }
-
-    foreach($quest->responses as $response) {
-        if (isset($response->title)) {
-            if ($response->title == 'correct') {
-                // only one answer possible for this qtype so first index is correct answer
-                $correct = $response->ident[0]['varequal'][0]['#'];
-            }
-        }
-        else {
-            // fallback method for when the title is not set
-            if ($response->feedback == 'correct') {
-               // only one answer possible for this qtype so first index is correct answer
-               $correct = $response->ident[0]['varequal'][0]['#']; // added [0]['varequal'][0]['#'] to $response->ident - CT 8/9/06
-            }
-        }
-    }
-
-    $i = 0;
-    foreach($quest->RESPONSE_BLOCK->choices as $response) {
-        $question->answer[$i] = $response->text;
-        if ($correct == $response->ident) {
-            $question->fraction[$i] = 1;
-            // this is a bit of a hack to catch the feedback... first we see if a 'correct' feedback exists
-            // then specific feedback for this question (maybe this should be switched?, but from my example
-            // question pools I have not seen response specific feedback, only correct or incorrect feedback
-            if (!empty($feedback['correct'])) {
-                $question->feedback[$i] = $feedback['correct'];
-            }
-            elseif (!empty($feedback[$i])) {
-                $question->feedback[$i] = $feedback[$i];
-            }
-            else {
-                // failsafe feedback (should be '' instead?)
-                $question->feedback[$i] = "correct";
-            }
-        }
-        else {
-            $question->fraction[$i] = 0;
-            if (!empty($feedback['incorrect'])) {
-                $question->feedback[$i] = $feedback['incorrect'];
-            }
-            elseif (!empty($feedback[$i])) {
-                $question->feedback[$i] = $feedback[$i];
-            }
-            else {
-                // failsafe feedback (should be '' instead?)
-                $question->feedback[$i] = 'incorrect';
-            }
-        }
-        $i++;
-    }
-
-    if (!empty($question)) {
-        $questions[] = $question;
-    }
-}
-
-//----------------------------------------
-// Process Multiple Choice Questions With Multiple Answers
-//----------------------------------------
-function process_ma($quest, &$questions) {
-    $question = $this->process_common( $quest ); // copied this from process_mc
-    $question->qtype = MULTICHOICE;
-    $question->single = 0; // More than one answer allowed
-
-    $answers = $quest->responses;
-    $correct_answers = array();
-    foreach($answers as $answer) {
-        if($answer->title == 'correct') {
-            $answerset = $answer->ident[0]['and'][0]['#']['varequal'];
-            foreach($answerset as $ans) {
-                $correct_answers[] = $ans['#'];
-            }
-        }
-    }
-
-    foreach ($quest->feedback as $fb) {
-        $feedback->{$fb->ident} = trim($fb->text);
-    }
-
-    $correct_answer_count = count($correct_answers);
-    $choiceset = $quest->RESPONSE_BLOCK->choices;
-    $i = 0;
-    foreach($choiceset as $choice) {
-        $question->answer[$i] = trim($choice->text);
-        if (in_array($choice->ident, $correct_answers)) {
-            // correct answer
-            $question->fraction[$i] = floor(100000/$correct_answer_count)/100000; // strange behavior if we have more than 5 decimal places
-            $question->feedback[$i] = $feedback->correct;
-        }
-        else {
-            // wrong answer
-            $question->fraction[$i] = 0;
-            $question->feedback[$i] = $feedback->incorrect;
-        }
-        $i++;
-    }
-
-    $questions[] = $question;
-}
-
-//----------------------------------------
-// Process Essay Questions
-//----------------------------------------
-function process_essay($quest, &$questions) {
-// this should be rewritten to accomodate moodle 1.6 essay question type eventually
-
-    if (defined("ESSAY")) {
-        // treat as short answer
-        $question = $this->process_common( $quest ); // copied this from process_mc
-        $question->qtype = ESSAY;
-
-        $question->feedback = array();
-        // not sure where to get the correct answer from
-        foreach($quest->feedback as $feedback) {
-            // Added this code to put the possible solution that the
-            // instructor gives as the Moodle answer for an essay question
-            if ($feedback->ident == 'solution') {
-                $question->feedback = $feedback->text;
-            }
-        }
-        //Added because essay/questiontype.php:save_question_option is expecting a
-        //fraction property - CT 8/10/06
-        $question->fraction[] = 1;
-        if (!empty($question)) {
-            $questions[]=$question;
-        }
-    }
-    else {
-        print "Essay question types are not handled because the quiz question type 'Essay' does not exist in this installation of Moodle<br/>";
-        print "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
-    }
-}
-
-//----------------------------------------
-// Process Matching Questions
-//----------------------------------------
-function process_matching($quest, &$questions) {
-    // renderedmatch is an optional plugin, so we need to check if it is defined
-    if (question_bank::is_qtype_installed('renderedmatch')) {
-        $question = $this->process_common($quest);
-        $question->valid = true;
-        $question->qtype = 'renderedmatch';
-
-        foreach($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) {
-            foreach($quest->responses as $rid => $resp) {
-                if ($resp->ident == $subq->ident) {
-                    $correct = $resp->correct;
-                    $feedback = $resp->feedback;
-                }
-            }
-
-            foreach($subq->choices as $cid => $choice) {
-                if ($choice == $correct) {
-                    $question->subquestions[] = $subq->text;
-                    $question->subanswers[] = $quest->RIGHT_MATCH_BLOCK->matching_answerset[$cid]->text;
-                }
-            }
-        }
-
-        // check format
-        $status = true;
-        if ( count($quest->RESPONSE_BLOCK->subquestions) > count($quest->RIGHT_MATCH_BLOCK->matching_answerset) || count($question->subquestions) < 2) {
-            $status = false;
-        }
-        else {
-            // need to redo to make sure that no two questions have the same answer (rudimentary now)
-            foreach($question->subanswers as $qstn) {
-                if(isset($previous)) {
-                    if ($qstn == $previous) {
-                        $status = false;
-                    }
-                }
-                $previous = $qstn;
-                if ($qstn == '') {
-                    $status = false;
-                }
-            }
-        }
-
-        if ($status) {
-            $questions[] = $question;
-        }
-        else {
-            global $COURSE, $CFG;
-            print '<table class="boxaligncenter" border="1">';
-            print '<tr><td colspan="2" style="background-color:#FF8888;">This matching question is malformed. Please ensure there are no blank answers, no two questions have the same answer, and/or there are correct answers for each question. There must be at least as many subanswers as subquestions, and at least one subquestion.</td></tr>';
-
-            print "<tr><td>Question:</td><td>".$quest->QUESTION_BLOCK->text;
-            if (isset($quest->QUESTION_BLOCK->file)) {
-                print '<br/><font color="red">There is a subfile contained in the zipfile that has been copied to course files: bb_import/'.basename($quest->QUESTION_BLOCK->file).'</font>';
-                if (preg_match('/(gif|jpg|jpeg|png)$/i', $quest->QUESTION_BLOCK->file)) {
-                    print '<img src="'.$CFG->wwwroot.'/file.php/'.$COURSE->id.'/bb_import/'.basename($quest->QUESTION_BLOCK->file).'" />';
-                }
-            }
-            print "</td></tr>";
-            print "<tr><td>Subquestions:</td><td><ul>";
-            foreach($quest->responses as $rs) {
-                $correct_responses->{$rs->ident} = $rs->correct;
-            }
-            foreach($quest->RESPONSE_BLOCK->subquestions as $subq) {
-                print '<li>'.$subq->text.'<ul>';
-                foreach($subq->choices as $id=>$choice) {
-                    print '<li>';
-                    if ($choice == $correct_responses->{$subq->ident}) {
-                        print '<font color="green">';
-                    }
-                    else {
-                        print '<font color="red">';
-                    }
-                    print $quest->RIGHT_MATCH_BLOCK->matching_answerset[$id]->text.'</font></li>';
-                }
-                print '</ul>';
-            }
-            print '</ul></td></tr>';
-
-            print '<tr><td>Feedback:</td><td><ul>';
-            foreach($quest->feedback as $fb) {
-                print '<li>'.$fb->ident.': '.$fb->text.'</li>';
-            }
-            print '</ul></td></tr></table>';
+            } else {
+                $this->error(get_string('cannotunzip', 'question'));
+                fulldelete($this->temp_dir);
+            }
+        } else {
+            $this->error(get_string('cannotreaduploadfile', 'error'));
+            fulldelete($this->tempdir);
+        }
+        return false;
+    }
+
+    /**
+     * Parse the array of strings into an array of questions.
+     * Each string is the content of a .dat questions file.
+     * This *could* burn memory - but it won't happen that much
+     * so fingers crossed!
+     * @param array of strings from the input file.
+     * @param stdClass $context
+     * @return array (of objects) question objects.
+     */
+    public function readquestions($lines) {
+
+        // Set up array to hold all our questions.
+        $questions = array();
+        if ($this->filetype == self::FILETYPE_QTI) {
+            $importer = new qformat_blackboard_six_qti();
+        } else if ($this->filetype == self::FILETYPE_POOL) {
+            $importer = new qformat_blackboard_six_pool();
+        } else {
+            // In all other cases we are not able to import the file.
+            return false;
         }
-    }
-    else {
-        print "Matching question types are not handled because the quiz question type 'Rendered Matching' does not exist in this installation of Moodle<br/>";
-        print "&nbsp;&nbsp;&nbsp;&nbsp;Omitted Question: ".$quest->QUESTION_BLOCK->text.'<br/><br/>';
-    }
-}
+        $importer->set_filebase($this->filebase);
 
-
-function strip_applet_tags_get_mathml($string) {
-    if(stristr($string, '</APPLET>') === FALSE) {
-        return $string;
-    }
-    else {
-        // strip all applet tags keeping stuff before/after and inbetween (if mathml) them
-        while (stristr($string, '</APPLET>') !== FALSE) {
-            preg_match("/(.*)\<applet.*value=\"(\<math\>.*\<\/math\>)\".*\<\/applet\>(.*)/i",$string, $mathmls);
-            $string = $mathmls[1].$mathmls[2].$mathmls[3];
+        // Each element of $lines is a string containing a complete xml document.
+        foreach ($lines as $text) {
+                $questions = array_merge($questions, $importer->readquestions($text));
         }
-        return $string;
+        return $questions;
     }
 }
-
-} // close object
-
diff --git a/question/format/blackboard_six/formatbase.php b/question/format/blackboard_six/formatbase.php
new file mode 100644 (file)
index 0000000..da08e91
--- /dev/null
@@ -0,0 +1,163 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Blackboard V5 and V6 question importer.
+ *
+ * @package    qformat_blackboard_six
+ * @copyright  2012 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class question import format for zip files with images
+ *
+ */
+
+class qformat_blackboard_six_base extends qformat_based_on_xml {
+    /** @var string path to path to root of image tree in unzipped archive. */
+    public $filebase = '';
+    /** @var string path to the temporary directory. */
+    public $tempdir = '';
+    /**
+     * This plugin provide import
+     * @return bool true
+     */
+    public function provide_import() {
+        return true;
+    }
+
+    /**
+     * Check if the given file is capable of being imported by this plugin.
+     * As {@link file_storage::mimetype()} now uses finfo PHP extension if available,
+     * the value returned by $file->get_mimetype for a .dat file is not the same on all servers.
+     * So we must made 2 checks to verify if the plugin can import the file.
+     * @param stored_file $file the file to check
+     * @return bool whether this plugin can import the file
+     */
+    public function can_import_file($file) {
+        $mimetypes = array(
+            mimeinfo('type', '.dat'),
+            mimeinfo('type', '.zip')
+        );
+        return in_array($file->get_mimetype(), $mimetypes) || in_array(mimeinfo('type', $file->get_filename()), $mimetypes);
+    }
+
+    public function mime_type() {
+        return mimeinfo('type', '.zip');
+    }
+
+    /**
+     * Does any post-processing that may be desired
+     * Clean the temporary directory if a zip file was imported
+     * @return bool success
+     */
+    public function importpostprocess() {
+        if ($this->tempdir != '') {
+            fulldelete($this->tempdir);
+        }
+        return true;
+    }
+    /**
+     * Set the path to the root of images tree
+     * @param string $path path to images root
+     */
+    public function set_filebase($path) {
+        $this->filebase = $path;
+    }
+
+    /**
+     * Store an image file in a draft filearea
+     * @param array $text, if itemid element don't exists it will be created
+     * @param string tempdir path to root of image tree
+     * @param string filepathinsidetempdir path to image in the tree
+     * @param string filename image's name
+     * @return string new name of the image as it was stored
+     */
+    protected function store_file_for_text_field(&$text, $tempdir, $filepathinsidetempdir, $filename) {
+        global $USER;
+        $fs = get_file_storage();
+        if (empty($text['itemid'])) {
+            $text['itemid'] = file_get_unused_draft_itemid();
+        }
+        // As question file areas don't support subdirs,
+        // convert path to filename.
+        // So that images with same name can be imported.
+        $newfilename = clean_param(str_replace('/', '__', $filepathinsidetempdir . '__' . $filename), PARAM_FILE);
+        $filerecord = array(
+            'contextid' => context_user::instance($USER->id)->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $text['itemid'],
+            'filepath'  => '/',
+            'filename'  => $newfilename,
+        );
+        $fs->create_file_from_pathname($filerecord, $tempdir . '/' . $filepathinsidetempdir . '/' . $filename);
+        return $newfilename;
+    }
+
+    /**
+     * Given an HTML text with references to images files,
+     * store all images in a draft filearea,
+     * and return an array with all urls in text recoded,
+     * format set to FORMAT_HTML, and itemid set to filearea itemid
+     * @param string text text to parse and recode
+     * @return array with keys text, format, itemid.
+     */
+    public function text_field($text) {
+        $data = array();
+        // Step one, find all file refs then add to array.
+        preg_match_all('|<img[^>]+src="([^"]*)"|i', $text, $out); // Find all src refs.
+
+        foreach ($out[1] as $path) {
+            $fullpath = $this->filebase . '/' . $path;
+
+            if (is_readable($fullpath)) {
+                $dirpath = dirname($path);
+                $filename = basename($path);
+                $newfilename = $this->store_file_for_text_field($data, $this->filebase, $dirpath, $filename);
+                $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+            }
+
+        }
+        $data['text'] = $text;
+        $data['format'] = FORMAT_HTML;
+        return $data;
+    }
+
+    /**
+     * Same as text_field but text is cleaned.
+     * @param string text text to parse and recode
+     * @return array with keys text, format, itemid.
+     */
+    public function cleaned_text_field($text) {
+        return $this->text_field($this->cleaninput($text));
+    }
+
+    /**
+     * Convert the question text to plain text.
+     * We need to overwrite this function because questiontext is an array.
+     */
+    protected function format_question_text($question) {
+        global $DB;
+        $formatoptions = new stdClass();
+        $formatoptions->noclean = true;
+        return html_to_text(format_text($question->questiontext['text'],
+                $question->questiontext['format'], $formatoptions), 0, false);
+    }
+}
diff --git a/question/format/blackboard_six/formatpool.php b/question/format/blackboard_six/formatpool.php
new file mode 100644 (file)
index 0000000..80867f3
--- /dev/null
@@ -0,0 +1,467 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Blackboard V5 and V6 question importer.
+ *
+ * @package    qformat_blackboard_six
+ * @copyright  2003 Scott Elliott
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/xmlize.php');
+
+/**
+ * Blackboard pool question importer.
+ *
+ * @copyright  2003 Scott Elliott
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+class qformat_blackboard_six_pool extends qformat_blackboard_six_base {
+    // Is the current question's question text escaped HTML (true for most if not all Blackboard files).
+    public $ishtml = true;
+
+    /**
+     * Parse the xml document into an array of questions
+     * this *could* burn memory - but it won't happen that much
+     * so fingers crossed!
+     * @param array of lines from the input file.
+     * @param stdClass $context
+     * @return array (of objects) questions objects.
+     */
+    protected function readquestions($text) {
+
+        // This converts xml to big nasty data structure,
+        // the 0 means keep white space as it is.
+        try {
+            $xml = xmlize($text, 0, 'UTF-8', true);
+        } catch (xml_format_exception $e) {
+            $this->error($e->getMessage(), '');
+            return false;
+        }
+
+        $questions = array();
+
+        $this->process_tf($xml, $questions);
+        $this->process_mc($xml, $questions);
+        $this->process_ma($xml, $questions);
+        $this->process_fib($xml, $questions);
+        $this->process_matching($xml, $questions);
+        $this->process_essay($xml, $questions);
+
+        return $questions;
+    }
+
+    /**
+     * Do question import processing common to every qtype.
+     * @param array $questiondata the xml tree related to the current question
+     * @return object initialized question object.
+     */
+    public function process_common($questiondata) {
+
+        // This routine initialises the question object.
+        $question = $this->defaultquestion();
+
+        // Determine if the question is already escaped html.
+        $this->ishtml = $this->getpath($questiondata,
+                array('#', 'BODY', 0, '#', 'FLAGS', 0, '#', 'ISHTML', 0, '@', 'value'),
+                false, false);
+
+        // Put questiontext in question object.
+        $text = $this->getpath($questiondata,
+                array('#', 'BODY', 0, '#', 'TEXT', 0, '#'),
+                '', true, get_string('importnotext', 'qformat_blackboard_six'));
+
+        $question->questiontext = $this->cleaned_text_field($text);
+        $question->questiontextformat = FORMAT_HTML; // Needed because add_blank_combined_feedback uses it.
+
+        // Put name in question object. We must ensure it is not empty and it is less than 250 chars.
+        $question->name = shorten_text(strip_tags($question->questiontext['text']), 200);
+        $question->name = substr($question->name, 0, 250);
+        if (!$question->name) {
+            $id = $this->getpath($questiondata,
+                    array('@', 'id'), '',  true);
+            $question->name = get_string('defaultname', 'qformat_blackboard_six' , $id);
+        }
+
+        $question->generalfeedback = '';
+        $question->generalfeedbackformat = FORMAT_HTML;
+        $question->generalfeedbackfiles = array();
+
+        // TODO : read the mark from the POOL TITLE QUESTIONLIST section.
+        $question->defaultmark = 1;
+        return $question;
+    }
+
+    /**
+     * Process Essay Questions
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_essay($xml, &$questions) {
+
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_ESSAY'), false, false)) {
+            $essayquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_ESSAY'), false, false);
+        } else {
+            return;
+        }
+
+        foreach ($essayquestions as $thisquestion) {
+
+            $question = $this->process_common($thisquestion);
+
+            $question->qtype = 'essay';
+
+            $question->answer = '';
+            $answer = $this->getpath($thisquestion,
+                    array('#', 'ANSWER', 0, '#', 'TEXT', 0, '#'), '', true);
+            $question->graderinfo =  $this->cleaned_text_field($answer);
+            $question->feedback = '';
+            $question->responseformat = 'editor';
+            $question->responsefieldlines = 15;
+            $question->attachments = 0;
+            $question->fraction = 0;
+
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process True / False Questions
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_tf($xml, &$questions) {
+
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_TRUEFALSE'), false, false)) {
+            $tfquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_TRUEFALSE'), false, false);
+        } else {
+            return;
+        }
+
+        foreach ($tfquestions as $thisquestion) {
+
+            $question = $this->process_common($thisquestion);
+
+            $question->qtype = 'truefalse';
+            $question->single = 1; // Only one answer is allowed.
+
+            $choices = $this->getpath($thisquestion, array('#', 'ANSWER'), array(), false);
+
+            $correctanswer = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'CORRECTANSWER', 0, '@', 'answer_id'),
+                    '', true);
+
+            // First choice is true, second is false.
+            $id = $this->getpath($choices[0], array('@', 'id'), '', true);
+            $correctfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'),
+                    '', true);
+            $incorrectfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'),
+                    '', true);
+            if (strcmp($id,  $correctanswer) == 0) {  // True is correct.
+                $question->answer = 1;
+                $question->feedbacktrue = $this->cleaned_text_field($correctfeedback);
+                $question->feedbackfalse = $this->cleaned_text_field($incorrectfeedback);
+            } else {  // False is correct.
+                $question->answer = 0;
+                $question->feedbacktrue = $this->cleaned_text_field($incorrectfeedback);
+                $question->feedbackfalse = $this->cleaned_text_field($correctfeedback);
+            }
+            $question->correctanswer = $question->answer;
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process Multiple Choice Questions with single answer
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_mc($xml, &$questions) {
+
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_MULTIPLECHOICE'), false, false)) {
+            $mcquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_MULTIPLECHOICE'), false, false);
+        } else {
+            return;
+        }
+
+        foreach ($mcquestions as $thisquestion) {
+
+            $question = $this->process_common($thisquestion);
+
+            $correctfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'),
+                    '', true);
+            $incorrectfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'),
+                    '', true);
+            $question->correctfeedback = $this->cleaned_text_field($correctfeedback);
+            $question->partiallycorrectfeedback = $this->text_field('');
+            $question->incorrectfeedback = $this->cleaned_text_field($incorrectfeedback);
+
+            $question->qtype = 'multichoice';
+            $question->single = 1; // Only one answer is allowed.
+
+            $choices = $this->getpath($thisquestion, array('#', 'ANSWER'), false, false);
+            $correctanswerid = $this->getpath($thisquestion,
+                        array('#', 'GRADABLE', 0, '#', 'CORRECTANSWER', 0, '@', 'answer_id'),
+                        '', true);
+            foreach ($choices as $choice) {
+                $choicetext = $this->getpath($choice, array('#', 'TEXT', 0, '#'), '', true);
+                // Put this choice in the question object.
+                $question->answer[] =  $this->cleaned_text_field($choicetext);
+
+                $choiceid = $this->getpath($choice, array('@', 'id'), '', true);
+                // If choice is the right answer, give 100% mark, otherwise give 0%.
+                if (strcmp ($choiceid, $correctanswerid) == 0) {
+                    $question->fraction[] = 1;
+                } else {
+                    $question->fraction[] = 0;
+                }
+                // There is never feedback specific to each choice.
+                $question->feedback[] =  $this->text_field('');
+            }
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process Multiple Choice Questions With Multiple Answers
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_ma($xml, &$questions) {
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_MULTIPLEANSWER'), false, false)) {
+            $maquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_MULTIPLEANSWER'), false, false);
+        } else {
+            return;
+        }
+
+        foreach ($maquestions as $thisquestion) {
+            $question = $this->process_common($thisquestion);
+
+            $correctfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'),
+                    '', true);
+            $incorrectfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'),
+                    '', true);
+            $question->correctfeedback = $this->cleaned_text_field($correctfeedback);
+            // As there is no partially correct feedback we use incorrect one.
+            $question->partiallycorrectfeedback = $this->cleaned_text_field($incorrectfeedback);
+            $question->incorrectfeedback = $this->cleaned_text_field($incorrectfeedback);
+
+            $question->qtype = 'multichoice';
+            $question->defaultmark = 1;
+            $question->single = 0; // More than one answers allowed.
+
+            $choices = $this->getpath($thisquestion, array('#', 'ANSWER'), false, false);
+            $correctanswerids = array();
+            foreach ($this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'CORRECTANSWER'), false, false) as $correctanswer) {
+                if ($correctanswer) {
+                    $correctanswerids[] = $this->getpath($correctanswer,
+                            array('@', 'answer_id'),
+                            '', true);
+                }
+            }
+            $fraction = 1/count($correctanswerids);
+
+            foreach ($choices as $choice) {
+                $choicetext = $this->getpath($choice, array('#', 'TEXT', 0, '#'), '', true);
+                // Put this choice in the question object.
+                $question->answer[] =  $this->cleaned_text_field($choicetext);
+
+                $choiceid = $this->getpath($choice, array('@', 'id'), '', true);
+
+                $iscorrect = in_array($choiceid, $correctanswerids);
+
+                if ($iscorrect) {
+                    $question->fraction[] = $fraction;
+                } else {
+                    $question->fraction[] = 0;
+                }
+                // There is never feedback specific to each choice.
+                $question->feedback[] =  $this->text_field('');
+            }
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process Fill in the Blank Questions
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_fib($xml, &$questions) {
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_FILLINBLANK'), false, false)) {
+            $fibquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_FILLINBLANK'), false, false);
+        } else {
+            return;
+        }
+
+        foreach ($fibquestions as $thisquestion) {
+
+            $question = $this->process_common($thisquestion);
+
+            $question->qtype = 'shortanswer';
+            $question->usecase = 0; // Ignore case.
+
+            $correctfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'),
+                    '', true);
+            $incorrectfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'),
+                    '', true);
+            $answers = $this->getpath($thisquestion, array('#', 'ANSWER'), false, false);
+            foreach ($answers as $answer) {
+                $question->answer[] = $this->getpath($answer,
+                        array('#', 'TEXT', 0, '#'), '', true);
+                $question->fraction[] = 1;
+                $question->feedback[] = $this->cleaned_text_field($correctfeedback);
+            }
+            $question->answer[] = '*';
+            $question->fraction[] = 0;
+            $question->feedback[] = $this->cleaned_text_field($incorrectfeedback);
+
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process Matching Questions
+     * @param array xml the xml tree
+     * @param array questions the questions already parsed
+     */
+    public function process_matching($xml, &$questions) {
+        if ($this->getpath($xml, array('POOL', '#', 'QUESTION_MATCH'), false, false)) {
+            $matchquestions = $this->getpath($xml,
+                    array('POOL', '#', 'QUESTION_MATCH'), false, false);
+        } else {
+            return;
+        }
+        // Blackboard questions can't be imported in core Moodle without a loss in data,
+        // as core match question don't allow HTML in subanswers. The contributed ddmatch
+        // question type support HTML in subanswers.
+        // The ddmatch question type is not part of core, so we need to check if it is defined.
+        $ddmatchisinstalled = question_bank::is_qtype_installed('ddmatch');
+
+        foreach ($matchquestions as $thisquestion) {
+
+            $question = $this->process_common($thisquestion);
+            if ($ddmatchisinstalled) {
+                $question->qtype = 'ddmatch';
+            } else {
+                $question->qtype = 'match';
+            }
+
+            $correctfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_CORRECT', 0, '#'),
+                    '', true);
+            $incorrectfeedback = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'FEEDBACK_WHEN_INCORRECT', 0, '#'),
+                    '', true);
+            $question->correctfeedback = $this->cleaned_text_field($correctfeedback);
+            // As there is no partially correct feedback we use incorrect one.
+            $question->partiallycorrectfeedback = $this->cleaned_text_field($incorrectfeedback);
+            $question->incorrectfeedback = $this->cleaned_text_field($incorrectfeedback);
+
+            $choices = $this->getpath($thisquestion,
+                    array('#', 'CHOICE'), false, false); // Blackboard "choices" are Moodle subanswers.
+            $answers = $this->getpath($thisquestion,
+                    array('#', 'ANSWER'), false, false); // Blackboard "answers" are Moodle subquestions.
+            $correctanswers = $this->getpath($thisquestion,
+                    array('#', 'GRADABLE', 0, '#', 'CORRECTANSWER'), false, false); // Mapping between choices and answers.
+            $mappings = array();
+            foreach ($correctanswers as $correctanswer) {
+                if ($correctanswer) {
+                    $correctchoiceid = $this->getpath($correctanswer,
+                                array('@', 'choice_id'), '', true);
+                    $correctanswerid = $this->getpath($correctanswer,
+                            array('@', 'answer_id'),
+                            '', true);
+                    $mappings[$correctanswerid] = $correctchoiceid;
+                }
+            }
+
+            foreach ($choices as $choice) {
+                if ($ddmatchisinstalled) {
+                    $choicetext = $this->cleaned_text_field($this->getpath($choice,
+                            array('#', 'TEXT', 0, '#'), '', true));
+                } else {
+                    $choicetext = trim(strip_tags($this->getpath($choice,
+                            array('#', 'TEXT', 0, '#'), '', true)));
+                }
+
+                if ($choicetext != '') { // Only import non empty subanswers.
+                    $subquestion = '';
+                    $choiceid = $this->getpath($choice,
+                            array('@', 'id'), '', true);
+                    $fiber = array_search($choiceid, $mappings);
+                    $fiber = array_keys ($mappings, $choiceid);
+                    foreach ($fiber as $correctanswerid) {
+                        // We have found a correspondance for this choice so we need to take the associated answer.
+                        foreach ($answers as $answer) {
+                            $currentanswerid = $this->getpath($answer,
+                                    array('@', 'id'), '', true);
+                            if (strcmp ($currentanswerid, $correctanswerid) == 0) {
+                                $subquestion = $this->getpath($answer,
+                                        array('#', 'TEXT', 0, '#'), '', true);
+                                break;
+                            }
+                        }
+                        $question->subquestions[] = $this->cleaned_text_field($subquestion);
+                        $question->subanswers[] = $choicetext;
+                    }
+
+                    if ($subquestion == '') { // Then in this case, $choice is a distractor.
+                        $question->subquestions[] = $this->text_field('');
+                        $question->subanswers[] = $choicetext;
+                    }
+                }
+            }
+
+            // Verify that this matching question has enough subquestions and subanswers.
+            $subquestioncount = 0;
+            $subanswercount = 0;
+            $subanswers = $question->subanswers;
+            foreach ($question->subquestions as $key => $subquestion) {
+                $subquestion = $subquestion['text'];
+                $subanswer = $subanswers[$key];
+                if ($subquestion != '') {
+                    $subquestioncount++;
+                }
+                $subanswercount++;
+            }
+            if ($subquestioncount < 2 || $subanswercount < 3) {
+                    $this->error(get_string('notenoughtsubans', 'qformat_blackboard_six', $question->questiontext['text']));
+            } else {
+                $questions[] = $question;
+            }
+
+        }
+    }
+}
diff --git a/question/format/blackboard_six/formatqti.php b/question/format/blackboard_six/formatqti.php
new file mode 100644 (file)
index 0000000..223d9f6
--- /dev/null
@@ -0,0 +1,894 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Blackboard V5 and V6 question importer.
+ *
+ * @package    qformat_blackboard_six
+ * @copyright  2005 Michael Penney
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/xmlize.php');
+
+/**
+ * Blackboard 6.0 question importer.
+ *
+ * @copyright  2005 Michael Penney
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qformat_blackboard_six_qti extends qformat_blackboard_six_base {
+    /**
+     * Parse the xml document into an array of questions
+     * this *could* burn memory - but it won't happen that much
+     * so fingers crossed!
+     * @param array of lines from the input file.
+     * @param stdClass $context
+     * @return array (of objects) questions objects.
+     */
+    protected function readquestions($text) {
+
+        // This converts xml to big nasty data structure,
+        // the 0 means keep white space as it is.
+        try {
+            $xml = xmlize($text, 0, 'UTF-8', true);
+        } catch (xml_format_exception $e) {
+            $this->error($e->getMessage(), '');
+            return false;
+        }
+
+        $questions = array();
+        // First step : we are only interested in the <item> tags.
+        $rawquestions = $this->getpath($xml,
+                array('questestinterop', '#', 'assessment', 0, '#', 'section', 0, '#', 'item'),
+                array(), false);
+        // Each <item> tag contains data related to a single question.
+        foreach ($rawquestions as $quest) {
+            // Second step : parse each question data into the intermediate
+            // rawquestion structure array.
+            // Warning : rawquestions are not Moodle questions.
+            $question = $this->create_raw_question($quest);
+            // Third step : convert a rawquestion into a Moodle question.
+            switch($question->qtype) {
+                case "Matching":
+                    $this->process_matching($question, $questions);
+                    break;
+                case "Multiple Choice":
+                    $this->process_mc($question, $questions);
+                    break;
+                case "Essay":
+                    $this->process_essay($question, $questions);
+                    break;
+                case "Multiple Answer":
+                    $this->process_ma($question, $questions);
+                    break;
+                case "True/False":
+                    $this->process_tf($question, $questions);
+                    break;
+                case 'Fill in the Blank':
+                    $this->process_fblank($question, $questions);
+                    break;
+                case 'Short Response':
+                    $this->process_essay($question, $questions);
+                    break;
+                default:
+                    $this->error(get_string('unknownorunhandledtype', 'qformat_blackboard_six', $question->qtype));
+                    break;
+            }
+        }
+        return $questions;
+    }
+
+    /**
+     * Creates a cleaner object to deal with for processing into Moodle.
+     * The object returned is NOT a moodle question object.
+     * @param array $quest XML <item> question  data
+     * @return object rawquestion
+     */
+    public function create_raw_question($quest) {
+
+        $rawquestion = new stdClass();
+        $rawquestion->qtype = $this->getpath($quest,
+                array('#', 'itemmetadata', 0, '#', 'bbmd_questiontype', 0, '#'),
+                '', true);
+        $rawquestion->id = $this->getpath($quest,
+                array('#', 'itemmetadata', 0, '#', 'bbmd_asi_object_id', 0, '#'),
+                '', true);
+        $presentation = new stdClass();
+        $presentation->blocks = $this->getpath($quest,
+                array('#', 'presentation', 0, '#', 'flow', 0, '#', 'flow'),
+                array(), false);
+
+        foreach ($presentation->blocks as $pblock) {
+            $block = new stdClass();
+            $block->type = $this->getpath($pblock,
+                    array('@', 'class'),
+                    '', true);
+
+            switch($block->type) {
+                case 'QUESTION_BLOCK':
+                    $subblocks = $this->getpath($pblock,
+                            array('#', 'flow'),
+                            array(), false);
+                    foreach ($subblocks as $sblock) {
+                        $this->process_block($sblock, $block);
+                    }
+                    break;
+
+                case 'RESPONSE_BLOCK':
+                    $choices = null;
+                    switch($rawquestion->qtype) {
+                        case 'Matching':
+                            $bbsubquestions = $this->getpath($pblock,
+                                    array('#', 'flow'),
+                                    array(), false);
+                            $sub_questions = array();
+                            foreach ($bbsubquestions as $bbsubquestion) {
+                                $sub_question = new stdClass();
+                                $sub_question->ident = $this->getpath($bbsubquestion,
+                                        array('#', 'response_lid', 0, '@', 'ident'),
+                                        '', true);
+                                $this->process_block($this->getpath($bbsubquestion,
+                                        array('#', 'flow', 0),
+                                        false, false), $sub_question);
+                                $bbchoices = $this->getpath($bbsubquestion,
+                                        array('#', 'response_lid', 0, '#', 'render_choice', 0,
+                                        '#', 'flow_label', 0, '#', 'response_label'),
+                                        array(), false);
+                                $choices = array();
+                                $this->process_choices($bbchoices, $choices);
+                                $sub_question->choices = $choices;
+                                if (!isset($block->subquestions)) {
+                                    $block->subquestions = array();
+                                }
+                                $block->subquestions[] = $sub_question;
+                            }
+                            break;
+                        case 'Multiple Answer':
+                            $bbchoices = $this->getpath($pblock,
+                                    array('#', 'response_lid', 0, '#', 'render_choice', 0, '#', 'flow_label'),
+                                    array(), false);
+                            $choices = array();
+                            $this->process_choices($bbchoices, $choices);
+                            $block->choices = $choices;
+                            break;
+                        case 'Essay':
+                            // Doesn't apply since the user responds with text input.
+                            break;
+                        case 'Multiple Choice':
+                            $mcchoices = $this->getpath($pblock,
+                                    array('#', 'response_lid', 0, '#', 'render_choice', 0, '#', 'flow_label'),
+                                    array(), false);
+                            foreach ($mcchoices as $mcchoice) {
+                                $choices = new stdClass();
+                                $choices = $this->process_block($mcchoice, $choices);
+                                $block->choices[] = $choices;
+                            }
+                            break;
+                        case 'Short Response':
+                            // Do nothing?
+                            break;
+                        case 'Fill in the Blank':
+                            // Do nothing?
+                            break;
+                        default:
+                            $bbchoices = $this->getpath($pblock,
+                                    array('#', 'response_lid', 0, '#', 'render_choice', 0, '#',
+                                    'flow_label', 0, '#', 'response_label'),
+                                    array(), false);
+                            $choices = array();
+                            $this->process_choices($bbchoices, $choices);
+                            $block->choices = $choices;
+                    }
+                    break;
+                case 'RIGHT_MATCH_BLOCK':
+                    $matchinganswerset = $this->getpath($pblock,
+                            array('#', 'flow'),
+                            false, false);
+
+                    $answerset = array();
+                    foreach ($matchinganswerset as $answer) {
+                        $bbanswer = new stdClass;
+                        $bbanswer->text =  $this->getpath($answer,
+                                array('#', 'flow', 0, '#', 'material', 0, '#', 'mat_extension',
+                                0, '#', 'mat_formattedtext', 0, '#'),
+                                false, false);
+                        $answerset[] = $bbanswer;
+                    }
+                    $block->matchinganswerset = $answerset;
+                    break;
+                default:
+                    $this->error(get_string('unhandledpresblock', 'qformat_blackboard_six'));
+                    break;
+            }
+            $rawquestion->{$block->type} = $block;
+        }
+
+        // Determine response processing.
+        // There is a section called 'outcomes' that I don't know what to do with.
+        $resprocessing = $this->getpath($quest,
+                array('#', 'resprocessing'),
+                array(), false);
+
+        $respconditions = $this->getpath($resprocessing[0],
+                array('#', 'respcondition'),
+                array(), false);
+        $responses = array();
+        if ($rawquestion->qtype == 'Matching') {
+            $this->process_matching_responses($respconditions, $responses);
+        } else {
+            $this->process_responses($respconditions, $responses);
+        }
+        $rawquestion->responses = $responses;
+        $feedbackset = $this->getpath($quest,
+                array('#', 'itemfeedback'),
+                array(), false);
+
+        $feedbacks = array();
+        $this->process_feedback($feedbackset, $feedbacks);
+        $rawquestion->feedback = $feedbacks;
+        return $rawquestion;
+    }
+
+    /**
+     * Helper function to process an XML block into an object.
+     * Can call himself recursively if necessary to parse this branch of the XML tree.
+     * @param array $curblock XML block to parse
+     * @return object $block parsed
+     */
+    public function process_block($curblock, $block) {
+
+        $curtype = $this->getpath($curblock,
+                array('@', 'class'),
+                '', true);
+
+        switch($curtype) {
+            case 'FORMATTED_TEXT_BLOCK':
+                $text = $this->getpath($curblock,
+                        array('#', 'material', 0, '#', 'mat_extension', 0, '#', 'mat_formattedtext', 0, '#'),
+                        '', true);
+                $block->text = $this->strip_applet_tags_get_mathml($text);
+                break;
+            case 'FILE_BLOCK':
+                $block->filename = $this->getpath($curblock,
+                        array('#', 'material', 0, '#'),
+                        '', true);
+                if ($block->filename != '') {
+                    // TODO : determine what to do with the file's content.
+                    $this->error(get_string('filenothandled', 'qformat_blackboard_six', $block->filename));
+                }
+                break;
+            case 'Block':
+                if ($this->getpath($curblock,
+                        array('#', 'material', 0, '#', 'mattext'),
+                        false, false)) {
+                    $block->text = $this->getpath($curblock,
+                            array('#', 'material', 0, '#', 'mattext', 0, '#'),
+                            '', true);
+                } else if ($this->getpath($curblock,
+                        array('#', 'material', 0, '#', 'mat_extension', 0, '#', 'mat_formattedtext'),
+                        false, false)) {
+                    $block->text = $this->getpath($curblock,
+                            array('#', 'material', 0, '#', 'mat_extension', 0, '#', 'mat_formattedtext', 0, '#'),
+                            '', true);
+                } else if ($this->getpath($curblock,
+                        array('#', 'response_label'),
+                        false, false)) {
+                    // This is a response label block.
+                    $subblocks = $this->getpath($curblock,
+                            array('#', 'response_label', 0),
+                            array(), false);
+                    if (!isset($block->ident)) {
+
+                        if ($this->getpath($subblocks,
+                                array('@', 'ident'), '', true)) {
+                            $block->ident = $this->getpath($subblocks,
+                                array('@', 'ident'), '', true);
+                        }
+                    }
+                    foreach ($this->getpath($subblocks,
+                            array('#', 'flow_mat'), array(), false) as $subblock) {
+                        $this->process_block($subblock, $block);
+                    }
+                } else {
+                    if ($this->getpath($curblock,
+                                array('#', 'flow_mat'), false, false)
+                            || $this->getpath($curblock,
+                                array('#', 'flow'), false, false)) {
+                        if ($this->getpath($curblock,
+                                array('#', 'flow_mat'), false, false)) {
+                            $subblocks = $this->getpath($curblock,
+                                    array('#', 'flow_mat'), array(), false);
+                        } else if ($this->getpath($curblock,
+                                array('#', 'flow'), false, false)) {
+                            $subblocks = $this->getpath($curblock,
+                                    array('#', 'flow'), array(), false);
+                        }
+                        foreach ($subblocks as $sblock) {
+                            // This will recursively grab the sub blocks which should be of one of the other types.
+                            $this->process_block($sblock, $block);
+                        }
+                    }
+                }
+                break;
+            case 'LINK_BLOCK':
+                // Not sure how this should be included?
+                $link = $this->getpath($curblock,
+                            array('#', 'material', 0, '#', 'mattext', 0, '@', 'uri'), '', true);
+                if (!empty($link)) {
+                    $block->link = $link;
+                } else {
+                    $block->link = '';
+                }
+                break;
+        }
+        return $block;
+    }
+
+    /**
+     * Preprocess XML blocks containing data for questions' choices.
+     * Called by {@link create_raw_question()}
+     * for matching, multichoice and fill in the blank questions.
+     * @param array $bbchoices XML block to parse
+     * @param array $choices array of choices suitable for a rawquestion.
+     */
+    protected function process_choices($bbchoices, &$choices) {
+        foreach ($bbchoices as $choice) {
+            if ($this->getpath($choice,
+                    array('@', 'ident'), '', true)) {
+                $curchoice = $this->getpath($choice,
+                        array('@', 'ident'), '', true);
+            } else { // For multiple answers.
+                $curchoice = $this->getpath($choice,
+                         array('#', 'response_label', 0), array(), false);
+            }
+            if ($this->getpath($choice,
+                    array('#', 'flow_mat', 0), false, false)) { // For multiple answers.
+                $curblock = $this->getpath($choice,
+                    array('#', 'flow_mat', 0), false, false);
+                // Reset $curchoice to new stdClass because process_block is expecting an object
+                // for the second argument and not a string,
+                // which is what is was set as originally - CT 8/7/06.
+                $curchoice = new stdClass();
+                $this->process_block($curblock, $curchoice);
+            } else if ($this->getpath($choice,
+                    array('#', 'response_label'), false, false)) {
+                // Reset $curchoice to new stdClass because process_block is expecting an object
+                // for the second argument and not a string,
+                // which is what is was set as originally - CT 8/7/06.
+                $curchoice = new stdClass();
+                $this->process_block($choice, $curchoice);
+            }
+            $choices[] = $curchoice;
+        }
+    }
+
+    /**
+     * Preprocess XML blocks containing data for subanswers
+     * Called by {@link create_raw_question()}
+     * for matching questions only.
+     * @param array $bbresponses XML block to parse
+     * @param array $responses array of responses suitable for a matching rawquestion.
+     */
+    protected function process_matching_responses($bbresponses, &$responses) {
+        foreach ($bbresponses as $bbresponse) {
+            $response = new stdClass;
+            if ($this->getpath($bbresponse,
+                    array('#', 'conditionvar', 0, '#', 'varequal'), false, false)) {
+                $response->correct = $this->getpath($bbresponse,
+                        array('#', 'conditionvar', 0, '#', 'varequal', 0, '#'), '', true);
+                $response->ident = $this->getpath($bbresponse,
+                        array('#', 'conditionvar', 0, '#', 'varequal', 0, '@', 'respident'), '', true);
+            }
+            // Suppressed an else block because if the above if condition is false,
+            // the question is not necessary a broken one, most of the time it's an <other> tag.
+
+            $response->feedback = $this->getpath($bbresponse,
+                    array('#', 'displayfeedback', 0, '@', 'linkrefid'), '', true);
+            $responses[] = $response;
+        }
+    }
+
+    /**
+     * Preprocess XML blocks containing data for responses processing.
+     * Called by {@link create_raw_question()}
+     * for all questions types.
+     * @param array $bbresponses XML block to parse
+     * @param array $responses array of responses suitable for a rawquestion.
+     */
+    protected function process_responses($bbresponses, &$responses) {
+        foreach ($bbresponses as $bbresponse) {
+            $response = new stdClass();
+            if ($this->getpath($bbresponse,
+                    array('@', 'title'), '', true)) {
+                $response->title = $this->getpath($bbresponse,
+                        array('@', 'title'), '', true);
+            } else {
+                $response->title = $this->getpath($bbresponse,
+                        array('#', 'displayfeedback', 0, '@', 'linkrefid'), '', true);
+            }
+            $response->ident = array();
+            if ($this->getpath($bbresponse,
+                    array('#', 'conditionvar', 0, '#'), false, false)) {
+                $response->ident[0] = $this->getpath($bbresponse,
+                        array('#', 'conditionvar', 0, '#'), array(), false);
+            } else if ($this->getpath($bbresponse,
+                    array('#', 'conditionvar', 0, '#', 'other', 0, '#'), false, false)) {
+                $response->ident[0] = $this->getpath($bbresponse,
+                        array('#', 'conditionvar', 0, '#', 'other', 0, '#'), array(), false);
+            }
+            if ($this->getpath($bbresponse,
+                    array('#', 'conditionvar', 0, '#', 'and'), false, false)) {
+                $responseset = $this->getpath($bbresponse,
+                    array('#', 'conditionvar', 0, '#', 'and'), array(), false);
+                foreach ($responseset as $rs) {
+                    $response->ident[] = $this->getpath($rs, array('#'), array(), false);
+                    if (!isset($response->feedback) and $this->getpath($rs, array('@'), false, false)) {
+                        $response->feedback = $this->getpath($rs,
+                                array('@', 'respident'), '', true);
+                    }
+                }
+            } else {
+                $response->feedback = $this->getpath($bbresponse,
+                        array('#', 'displayfeedback', 0, '@', 'linkrefid'), '', true);
+            }
+
+            // Determine what fraction to give response.
+            if ($this->getpath($bbresponse,
+                        array('#', 'setvar'), false, false)) {
+                switch ($this->getpath($bbresponse,
+                        array('#', 'setvar', 0, '#'), false, false)) {
+                    case "SCORE.max":
+                        $response->fraction = 1;
+                        break;
+                    default:
+                        // I have only seen this being 0 or unset.
+                        // There are probably fractional values of SCORE.max, but I'm not sure what they look like.
+                        $response->fraction = 0;
+                        break;
+                }
+            } else {
+                // Just going to assume this is the case this is probably not correct.
+                $response->fraction = 0;
+            }
+
+            $responses[] = $response;
+        }
+    }
+
+    /**
+     * Preprocess XML blocks containing data for responses feedbacks.
+     * Called by {@link create_raw_question()}
+     * for all questions types.
+     * @param array $feedbackset XML block to parse
+     * @param array $feedbacks array of feedbacks suitable for a rawquestion.
+     */
+    public function process_feedback($feedbackset, &$feedbacks) {
+        foreach ($feedbackset as $bb_feedback) {
+            $feedback = new stdClass();
+            $feedback->ident = $this->getpath($bb_feedback,
+                    array('@', 'ident'), '', true);
+            $feedback->text = '';
+            if ($this->getpath($bb_feedback,
+                    array('#', 'flow_mat', 0), false, false)) {
+                $this->process_block($this->getpath($bb_feedback,
+                        array('#', 'flow_mat', 0), false, false), $feedback);
+            } else if ($this->getpath($bb_feedback,
+                    array('#', 'solution', 0, '#', 'solutionmaterial', 0, '#', 'flow_mat', 0), false, false)) {
+                $this->process_block($this->getpath($bb_feedback,
+                        array('#', 'solution', 0, '#', 'solutionmaterial', 0, '#', 'flow_mat', 0), false, false), $feedback);
+            }
+
+            $feedbacks[$feedback->ident] = $feedback;
+        }
+    }
+
+    /**
+     * Create common parts of question
+     * @param object $quest rawquestion
+     * @return object Moodle question.
+     */
+    public function process_common($quest) {
+        $question = $this->defaultquestion();
+        $text = $quest->QUESTION_BLOCK->text;
+
+        $question->questiontext = $this->cleaned_text_field($text);
+        $question->questiontextformat = FORMAT_HTML; // Needed because add_blank_combined_feedback uses it.
+
+        $question->name = shorten_text(strip_tags($question->questiontext['text']), 200);
+        $question->name = substr($question->name, 0, 250);
+        if (!$question->name) {
+            $question->name = get_string('defaultname', 'qformat_blackboard_six' , $quest->id);
+        }
+        $question->generalfeedback = '';
+        $question->generalfeedbackformat = FORMAT_HTML;
+        $question->generalfeedbackfiles = array();
+
+        return $question;
+    }
+
+    /**
+     * Process True / False Questions
+     * Parse a truefalse rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    protected function process_tf($quest, &$questions) {
+        $question = $this->process_common($quest);
+
+        $question->qtype = 'truefalse';
+        $question->single = 1; // Only one answer is allowed.
+        $question->penalty = 1; // Penalty = 1 for truefalse questions.
+        // 0th [response] is the correct answer.
+        $responses = $quest->responses;
+        $correctresponse = $this->getpath($responses[0]->ident[0],
+                array('varequal', 0, '#'), '', true);
+        if ($correctresponse != 'false') {
+            $correct = true;
+        } else {
+            $correct = false;
+        }
+        $fback = new stdClass();
+
+        foreach ($quest->feedback as $fb) {
+            $fback->{$fb->ident} = $fb->text;
+        }
+
+        if ($correct) {  // True is correct.
+            $question->answer = 1;
+            $question->feedbacktrue = $this->cleaned_text_field($fback->correct);
+            $question->feedbackfalse = $this->cleaned_text_field($fback->incorrect);
+        } else {  // False is correct.
+            $question->answer = 0;
+            $question->feedbacktrue = $this->cleaned_text_field($fback->incorrect);
+            $question->feedbackfalse = $this->cleaned_text_field($fback->correct);
+        }
+        $question->correctanswer = $question->answer;
+        $questions[] = $question;
+    }
+
+    /**
+     * Process Fill in the Blank Questions
+     * Parse a fillintheblank rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    protected function process_fblank($quest, &$questions) {
+        $question = $this->process_common($quest);
+        $question->qtype = 'shortanswer';
+        $question->usecase = 0; // Ignore case.
+
+        $answers = array();
+        $fractions = array();
+        $feedbacks = array();
+
+        // Extract the feedback.
+        $feedback = array();
+        foreach ($quest->feedback as $fback) {
+            if (isset($fback->ident)) {
+                if ($fback->ident == 'correct' || $fback->ident == 'incorrect') {
+                    $feedback[$fback->ident] = $fback->text;
+                }
+            }
+        }
+
+        foreach ($quest->responses as $response) {
+            if (isset($response->title)) {
+                if ($this->getpath($response->ident[0],
+                        array('varequal', 0, '#'), false, false)) {
+                    // For BB Fill in the Blank, only interested in correct answers.
+                    if ($response->feedback = 'correct') {
+                        $answers[] = $this->getpath($response->ident[0],
+                                array('varequal', 0, '#'), '', true);
+                        $fractions[] = 1;
+                        if (isset($feedback['correct'])) {
+                            $feedbacks[] = $this->cleaned_text_field($feedback['correct']);
+                        } else {
+                            $feedbacks[] = $this->text_field('');
+                        }
+                    }
+                }
+
+            }
+        }
+
+        // Adding catchall to so that students can see feedback for incorrect answers when they enter something,
+        // the instructor did not enter.
+        $answers[] = '*';
+        $fractions[] = 0;
+        if (isset($feedback['incorrect'])) {
+            $feedbacks[] = $this->cleaned_text_field($feedback['incorrect']);
+        } else {
+            $feedbacks[] = $this->text_field('');
+        }
+
+        $question->answer = $answers;
+        $question->fraction = $fractions;
+        $question->feedback = $feedbacks; // Changed to assign $feedbacks to $question->feedback instead of.
+
+        if (!empty($question)) {
+            $questions[] = $question;
+        }
+
+    }
+
+    /**
+     * Process Multichoice Questions
+     * Parse a multichoice single answer rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    protected function process_mc($quest, &$questions) {
+        $question = $this->process_common($quest);
+        $question->qtype = 'multichoice';
+        $question = $this->add_blank_combined_feedback($question);
+        $question->single = 1;
+        $feedback = array();
+        foreach ($quest->feedback as $fback) {
+            $feedback[$fback->ident] = $fback->text;
+        }
+
+        foreach ($quest->responses as $response) {
+            if (isset($response->title)) {
+                if ($response->title == 'correct') {
+                    // Only one answer possible for this qtype so first index is correct answer.
+                    $correct = $this->getpath($response->ident[0],
+                            array('varequal', 0, '#'), '', true);
+                }
+            } else {
+                // Fallback method for when the title is not set.
+                if ($response->feedback == 'correct') {
+                    // Only one answer possible for this qtype so first index is correct answer.
+                    $correct = $this->getpath($response->ident[0],
+                            array('varequal', 0, '#'), '', true);
+                }
+            }
+        }
+
+        $i = 0;
+        foreach ($quest->RESPONSE_BLOCK->choices as $response) {
+            $question->answer[$i] = $this->cleaned_text_field($response->text);
+            if ($correct == $response->ident) {
+                $question->fraction[$i] = 1;
+                // This is a bit of a hack to catch the feedback... first we see if a  'specific'
+                // feedback for this response exists, then if a 'correct' feedback exists.
+
+                if (!empty($feedback[$response->ident]) ) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback[$response->ident]);
+                } else if (!empty($feedback['correct'])) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback['correct']);
+                } else if (!empty($feedback[$i])) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback[$i]);
+                } else {
+                    $question->feedback[$i] = $this->cleaned_text_field(get_string('correct', 'question'));
+                }
+            } else {
+                $question->fraction[$i] = 0;
+                if (!empty($feedback[$response->ident]) ) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback[$response->ident]);
+                } else if (!empty($feedback['incorrect'])) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback['incorrect']);
+                } else if (!empty($feedback[$i])) {
+                    $question->feedback[$i] = $this->cleaned_text_field($feedback[$i]);
+                } else {
+                    $question->feedback[$i] = $this->cleaned_text_field(get_string('incorrect', 'question'));
+                }
+            }
+            $i++;
+        }
+
+        if (!empty($question)) {
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Process Multiple Choice Questions With Multiple Answers.
+     * Parse a multichoice multianswer rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    public function process_ma($quest, &$questions) {
+        $question = $this->process_common($quest);
+        $question->qtype = 'multichoice';
+        $question = $this->add_blank_combined_feedback($question);
+        $question->single = 0; // More than one answer allowed.
+
+        $answers = $quest->responses;
+        $correctanswers = array();
+        foreach ($answers as $answer) {
+            if ($answer->title == 'correct') {
+                $answerset = $this->getpath($answer->ident[0],
+                        array('and', 0, '#', 'varequal'), array(), false);
+                foreach ($answerset as $ans) {
+                    $correctanswers[] = $ans['#'];
+                }
+            }
+        }
+        $feedback = new stdClass();
+        foreach ($quest->feedback as $fb) {
+            $feedback->{$fb->ident} = trim($fb->text);
+        }
+
+        $correctanswercount = count($correctanswers);
+        $fraction = 1/$correctanswercount;
+        $choiceset = $quest->RESPONSE_BLOCK->choices;
+        $i = 0;
+        foreach ($choiceset as $choice) {
+            $question->answer[$i] = $this->cleaned_text_field(trim($choice->text));
+            if (in_array($choice->ident, $correctanswers)) {
+                // Correct answer.
+                $question->fraction[$i] = $fraction;
+                $question->feedback[$i] = $this->cleaned_text_field($feedback->correct);
+            } else {
+                // Wrong answer.
+                $question->fraction[$i] = 0;
+                $question->feedback[$i] = $this->cleaned_text_field($feedback->incorrect);
+            }
+            $i++;
+        }
+
+        $questions[] = $question;
+    }
+
+    /**
+     * Process Essay Questions
+     * Parse an essay rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    public function process_essay($quest, &$questions) {
+
+        $question = $this->process_common($quest);
+        $question->qtype = 'essay';
+
+        $question->feedback = array();
+        // Not sure where to get the correct answer from?
+        foreach ($quest->feedback as $feedback) {
+            // Added this code to put the possible solution that the
+            // instructor gives as the Moodle answer for an essay question.
+            if ($feedback->ident == 'solution') {
+                $question->graderinfo = $this->cleaned_text_field($feedback->text);
+            }
+        }
+        // Added because essay/questiontype.php:save_question_option is expecting a
+        // fraction property - CT 8/10/06.
+        $question->fraction[] = 1;
+        $question->defaultmark = 1;
+        $question->responseformat = 'editor';
+        $question->responsefieldlines = 15;
+        $question->attachments = 0;
+
+        $questions[]=$question;
+    }
+
+    /**
+     * Process Matching Questions
+     * Parse a matching rawquestion and add the result
+     * to the array of questions already parsed.
+     * @param object $quest rawquestion
+     * @param $questions array of Moodle questions already done.
+     */
+    public function process_matching($quest, &$questions) {
+
+        // Blackboard matching questions can't be imported in core Moodle without a loss in data,
+        // as core match question don't allow HTML in subanswers. The contributed ddmatch
+        // question type support HTML in subanswers.
+        // The ddmatch question type is not part of core, so we need to check if it is defined.
+        $ddmatchisinstalled = question_bank::is_qtype_installed('ddmatch');
+
+        $question = $this->process_common($quest);
+        $question = $this->add_blank_combined_feedback($question);
+        $question->valid = true;
+        if ($ddmatchisinstalled) {
+            $question->qtype = 'ddmatch';
+        } else {
+            $question->qtype = 'match';
+        }
+        // Construction of the array holding mappings between subanswers and subquestions.
+        foreach ($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) {
+            foreach ($quest->responses as $rid => $resp) {
+                if (isset($resp->ident) && $resp->ident == $subq->ident) {
+                    $correct = $resp->correct;
+                }
+            }
+
+            foreach ($subq->choices as $cid => $choice) {
+                if ($choice == $correct) {
+                    $mappings[$subq->ident] = $cid;
+                }
+            }
+        }
+
+        foreach ($subq->choices as $choiceid => $choice) {
+            $subanswertext = $quest->RIGHT_MATCH_BLOCK->matchinganswerset[$choiceid]->text;
+            if ($ddmatchisinstalled) {
+                $subanswer = $this->cleaned_text_field($subanswertext);
+            } else {
+                $subanswertext = html_to_text($this->cleaninput($subanswertext), 0);
+                $subanswer = $subanswertext;
+            }
+
+            if ($subanswertext != '') { // Only import non empty subanswers.
+                $subquestion = '';
+
+                $fiber = array_keys ($mappings, $choiceid);
+                foreach ($fiber as $correctanswerid) {
+                    // We have found a correspondance for this subanswer so we need to take the associated subquestion.
+                    foreach ($quest->RESPONSE_BLOCK->subquestions as $qid => $subq) {
+                        $currentsubqid = $subq->ident;
+                        if (strcmp ($currentsubqid, $correctanswerid) == 0) {
+                            $subquestion = $subq->text;
+                            break;
+                        }
+                    }
+                    $question->subquestions[] = $this->cleaned_text_field($subquestion);
+                    $question->subanswers[] = $subanswer;
+                }
+
+                if ($subquestion == '') { // Then in this case, $choice is a distractor.
+                    $question->subquestions[] = $this->text_field('');
+                    $question->subanswers[] = $subanswer;
+                }
+            }
+        }
+
+        // Verify that this matching question has enough subquestions and subanswers.
+        $subquestioncount = 0;
+        $subanswercount = 0;
+        $subanswers = $question->subanswers;
+        foreach ($question->subquestions as $key => $subquestion) {
+            $subquestion = $subquestion['text'];
+            $subanswer = $subanswers[$key];
+            if ($subquestion != '') {
+                $subquestioncount++;
+            }
+            $subanswercount++;
+        }
+        if ($subquestioncount < 2 || $subanswercount < 3) {
+                $this->error(get_string('notenoughtsubans', 'qformat_blackboard_six', $question->questiontext['text']));
+        } else {
+            $questions[] = $question;
+        }
+    }
+
+    /**
+     * Strip the applet tag used by Blackboard to render mathml formulas,
+     * keeping the mathml tag.
+     * @param string $string
+     * @return string
+     */
+    public function strip_applet_tags_get_mathml($string) {
+        if (stristr($string, '</APPLET>') === false) {
+            return $string;
+        } else {
+            // Strip all applet tags keeping stuff before/after and inbetween (if mathml) them.
+            while (stristr($string, '</APPLET>') !== false) {
+                preg_match("/(.*)\<applet.*value=\"(\<math\>.*\<\/math\>)\".*\<\/applet\>(.*)/i", $string, $mathmls);
+                $string = $mathmls[1].$mathmls[2].$mathmls[3];
+            }
+            return $string;
+        }
+    }
+
+}
index 75ff1e1..020c7a2 100644 (file)
 /**
  * Strings for component 'qformat_blackboard_six', language 'en', branch 'MOODLE_20_STABLE'
  *
- * @package    qformat
- * @subpackage blackboard_six
+ * @package    qformat_blackboard_six
  * @copyright  2010 Helen Foster
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['defaultname'] = 'Imported question {$a}';
+$string['errormanifest'] = 'Error while parsing the IMS manifest document';
+$string['importnotext'] = 'Missing question text in XML file';
+$string['filenothandled'] = 'This archive contains reference to a file material {$a} wich is not currently handled by import';
+$string['imagenotfound'] = 'Image file with path {$a} was not found in the import.';
+$string['notenoughtsubans'] = 'Unable to import matching question \'{$a}\' because a matching question must comprise at least two questions and three answers.';
 $string['pluginname'] = 'Blackboard V6+';
-$string['pluginname_help'] = 'Blackboard V6+ format enables questions saved in Blackboard\'s export format to be imported via zip file. It provides limited support for Blackboard Version 6 and 7.';
+$string['pluginname_help'] = 'Blackboard V6+ format enables questions saved in all Blackboard export formats to be imported via a dat or zip file. For zip files, images import is supported.';
+$string['unhandledpresblock'] = 'Unhandled presentation bloc';
+$string['unknownorunhandledtype'] = 'Unknown or unhandled question type: {$a}';
diff --git a/question/format/blackboard_six/tests/blackboardformatpool_test.php b/question/format/blackboard_six/tests/blackboardformatpool_test.php
new file mode 100644 (file)
index 0000000..22cadb7
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the Moodle Blackboard V6+ format.
+ *
+ * @package    qformat_blackboard_six
+ * @copyright  2012 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->dirroot . '/question/format.php');
+require_once($CFG->dirroot . '/question/format/blackboard_six/format.php');
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+
+
+/**
+ * Unit tests for the blackboard question import format.
+ *
+ * @copyright  2012 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qformat_blackboard_six_pool_test extends question_testcase {
+
+    public function make_test_xml() {
+        $xml = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_pool.dat');
+        return array(0=>$xml);
+    }
+
+    public function test_import_match() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+
+        $q = $questions[4];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'match';
+        $expectedq->name = 'Classify the animals.';
+        $expectedq->questiontext = array(
+                'text' => '<i>Classify the animals.</i>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array('text' => '',
+                'format' => FORMAT_HTML);
+        $expectedq->partiallycorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML);
+        $expectedq->incorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML);
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->subquestions = array(
+            array('text' => 'cat', 'format' => FORMAT_HTML),
+            array('text' => '', 'format' => FORMAT_HTML),
+            array('text' => 'frog', 'format' => FORMAT_HTML),
+            array('text' => 'newt', 'format' => FORMAT_HTML));
+        $expectedq->subanswers = array('mammal', 'insect', 'amphibian', 'amphibian');
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_single() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[1];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 1;
+        $expectedq->name = 'What\'s between orange and green in the spectrum?';
+        $expectedq->questiontext = array(
+                'text' =>'<span style="font-size:12pt">What\'s between orange and green in the spectrum?</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array('text' => 'You gave the right answer.',
+                'format' => FORMAT_HTML);
+        $expectedq->partiallycorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML);
+        $expectedq->incorrectfeedback = array('text' => 'Only yellow is between orange and green in the spectrum.',
+                'format' => FORMAT_HTML);
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                0 => array(
+                    'text' => '<span style="font-size:12pt">red</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '<span style="font-size:12pt">yellow</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '<span style="font-size:12pt">blue</span>',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(0, 1, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_multi() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[2];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 0;
+        $expectedq->name = 'What\'s between orange and green in the spectrum?';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">What\'s between orange and green in the spectrum?</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => 'You gave the right answer.',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => 'Only yellow and off-beige are between orange and green in the spectrum.',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => 'Only yellow and off-beige are between orange and green in the spectrum.',
+                'format' => FORMAT_HTML,
+                );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                0 => array(
+                    'text' => '<span style="font-size:12pt">yellow</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '<span style="font-size:12pt">red</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '<span style="font-size:12pt">off-beige</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => '<span style="font-size:12pt">blue</span>',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(0.5, 0, 0.5, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_truefalse() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[0];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'truefalse';
+        $expectedq->name = '42 is the Absolute Answer to everything.';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">42 is the Absolute Answer to everything.</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->correctanswer = 0;
+        $expectedq->feedbacktrue = array(
+                'text' => '42 is the Ultimate Answer.',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->feedbackfalse = array(
+                'text' => 'You gave the right answer.',
+                'format' => FORMAT_HTML,
+            );
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_fill_in_the_blank() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[3];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'shortanswer';
+        $expectedq->name = 'Name an amphibian: __________.';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">Name an amphibian: __________.</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->usecase = 0;
+        $expectedq->answer = array('frog', '*');
+        $expectedq->fraction = array(1, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_essay() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(2);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[5];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'essay';
+        $expectedq->name = 'How are you?';
+        $expectedq->questiontext = array(
+                'text' => 'How are you?',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->responseformat = 'editor';
+        $expectedq->responsefieldlines = 15;
+        $expectedq->attachments = 0;
+        $expectedq->graderinfo = array(
+                'text' => 'Blackboard answer for essay questions will be imported as informations for graders.',
+                'format' => FORMAT_HTML,
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+}
diff --git a/question/format/blackboard_six/tests/blackboardsixformatqti_test.php b/question/format/blackboard_six/tests/blackboardsixformatqti_test.php
new file mode 100644 (file)
index 0000000..68df181
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for the Moodle Blackboard V6+ format.
+ *
+ * @package    qformat_blackboard_six
+ * @copyright  2012 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->dirroot . '/question/format.php');
+require_once($CFG->dirroot . '/question/format/blackboard_six/format.php');
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+
+
+/**
+ * Unit tests for the blackboard question import format.
+ *
+ * @copyright  2012 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qformat_blackboard_six_qti_test extends question_testcase {
+
+    public function make_test_xml() {
+        $xml = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_qti.dat');
+        return array(0=>$xml);
+    }
+    public function test_import_match() {
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[3];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'match';
+        $expectedq->name = 'Classify the animals.';
+        $expectedq->questiontext = array(
+                'text' => 'Classify the animals.',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->partiallycorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->incorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->subquestions = array(
+            array('text' => '', 'format' => FORMAT_HTML),
+            array('text' => 'cat', 'format' => FORMAT_HTML),
+            array('text' => 'frog', 'format' => FORMAT_HTML),
+            array('text' => 'newt', 'format' => FORMAT_HTML));
+        $expectedq->subanswers = array('insect', 'mammal', 'amphibian', 'amphibian');
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_single() {
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[1];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 1;
+        $expectedq->name = 'What\'s between orange and green in the spectrum?';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">What\'s between orange and green in the spectrum?</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->partiallycorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->incorrectfeedback = array('text' => '',
+                'format' => FORMAT_HTML, 'files' => array());
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                0 => array(
+                    'text' => '<span style="font-size:12pt">red</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '<span style="font-size:12pt">yellow</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '<span style="font-size:12pt">blue</span>',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(0, 1, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => 'Red is not between orange and green in the spectrum but yellow is.',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => 'You gave the right answer.',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'Blue is not between orange and green in the spectrum but yellow is.',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_multi() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[2];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 0;
+        $expectedq->name = 'What\'s between orange and green in the spectrum?';
+        $expectedq->questiontext = array(
+                'text' => '<i>What\'s between orange and green in the spectrum?</i>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                0 => array(
+                    'text' => '<span style="font-size:12pt">yellow</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '<span style="font-size:12pt">red</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '<span style="font-size:12pt">off-beige</span>',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => '<span style="font-size:12pt">blue</span>',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(0.5, 0, 0.5, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_truefalse() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[0];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'truefalse';
+        $expectedq->name = '42 is the Absolute Answer to everything.';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">42 is the Absolute Answer to everything.</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->correctanswer = 0;
+        $expectedq->feedbacktrue = array(
+                'text' => '42 is the <b>Ultimate</b> Answer.',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->feedbackfalse = array(
+                'text' => 'You gave the right answer.',
+                'format' => FORMAT_HTML,
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_fill_in_the_blank() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[4];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'shortanswer';
+        $expectedq->name = 'Name an amphibian: __________.';
+        $expectedq->questiontext = array(
+                'text' => '<span style="font-size:12pt">Name an amphibian: __________.</span>',
+                'format' => FORMAT_HTML,
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->usecase = 0;
+        $expectedq->answer = array('frog', '*');
+        $expectedq->fraction = array(1, 0);
+        $expectedq->feedback = array(
+                0 => array(
+                    'text' => 'A frog is an amphibian.',
+                    'format' => FORMAT_HTML,
+                ),
+                1 => array(
+                    'text' => 'A frog is an amphibian.',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_essay() {
+
+        $xml = $this->make_test_xml();
+
+        $importer = new qformat_blackboard_six();
+        $importer->set_filetype(1);
+        $questions = $importer->readquestions($xml);
+        $q = $questions[5];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'essay';
+        $expectedq->name = 'How are you?';
+        $expectedq->questiontext = array(
+                'text' => 'How are you?',
+                'format' => FORMAT_HTML
+            );
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->responseformat = 'editor';
+        $expectedq->responsefieldlines = 15;
+        $expectedq->attachments = 0;
+        $expectedq->graderinfo = array(
+                'text' => 'Blackboard answer for essay questions will be imported as informations for graders.',
+                'format' => FORMAT_HTML,
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+}
diff --git a/question/format/blackboard_six/tests/fixtures/sample_blackboard_pool.dat b/question/format/blackboard_six/tests/fixtures/sample_blackboard_pool.dat
new file mode 100644 (file)
index 0000000..93bb583
--- /dev/null
@@ -0,0 +1,142 @@
+<?xml version='1.0' encoding='utf-8'?>\r
+<POOL>\r
+    <TITLE value='exam 3 2008-9'/>\r
+    <QUESTIONLIST>\r
+        <QUESTION id='q1' class='QUESTION_TRUEFALSE' points='1'/>\r
+        <QUESTION id='q7' class='QUESTION_MULTIPLECHOICE' points='1'/>\r
+        <QUESTION id='q8' class='QUESTION_MULTIPLEANSWER' points='1'/>\r
+        <QUESTION id='q39-44' class='QUESTION_MATCH' points='1'/>\r
+        <QUESTION id='q9' class='QUESTION_ESSAY' points='1'/>\r
+        <QUESTION id='q27' class='QUESTION_FILLINBLANK' points='1'/>\r
+    </QUESTIONLIST>\r
+    <QUESTION_TRUEFALSE id='q1'>\r
+        <BODY>\r
+            <TEXT><![CDATA[<span style="font-size:12pt">42 is the Absolute Answer to everything.</span>]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q1_a1'>\r
+            <TEXT>False</TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q1_a2'>\r
+            <TEXT>True</TEXT>\r
+        </ANSWER>\r
+        <GRADABLE>\r
+            <CORRECTANSWER answer_id='q1_a2'/>\r
+            <FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>\r
+            <FEEDBACK_WHEN_INCORRECT><![CDATA[42 is the Ultimate Answer.]]></FEEDBACK_WHEN_INCORRECT>\r
+        </GRADABLE>\r
+    </QUESTION_TRUEFALSE>\r
+    <QUESTION_MULTIPLECHOICE id='q7'>\r
+        <BODY>\r
+            <TEXT><![CDATA[<span style="font-size:12pt">What's between orange and green in the spectrum?</span>]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q7_a1' position='1'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">red</span>]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q7_a2' position='2'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">yellow</span>]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q7_a3' position='3'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">blue</span>]]></TEXT>\r
+        </ANSWER>\r
+        <GRADABLE>\r
+            <CORRECTANSWER answer_id='q7_a2'/>\r
+            <FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>\r
+            <FEEDBACK_WHEN_INCORRECT><![CDATA[Only yellow is between orange and green in the spectrum.]]></FEEDBACK_WHEN_INCORRECT>\r
+        </GRADABLE>\r
+    </QUESTION_MULTIPLECHOICE>\r
+    <QUESTION_MULTIPLEANSWER id='q8'>\r
+        <BODY>\r
+            <TEXT><![CDATA[<span style="font-size:12pt">What's between orange and green in the spectrum?</span>]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q8_a1' position='1'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">yellow</span>]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q8_a2' position='2'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">red</span>]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q8_a3' position='3'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">off-beige</span>]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q8_a4' position='4'>\r
+        <TEXT><![CDATA[<span style="font-size:12pt">blue</span>]]></TEXT>\r
+        </ANSWER>\r
+        <GRADABLE>\r
+            <CORRECTANSWER answer_id='q8_a1'/>\r
+            <CORRECTANSWER answer_id='q8_a3'/>\r
+            <FEEDBACK_WHEN_CORRECT><![CDATA[You gave the right answer.]]></FEEDBACK_WHEN_CORRECT>\r
+            <FEEDBACK_WHEN_INCORRECT><![CDATA[Only yellow and off-beige are between orange and green in the spectrum.]]></FEEDBACK_WHEN_INCORRECT>\r
+        </GRADABLE>\r
+    </QUESTION_MULTIPLEANSWER>\r
+    <QUESTION_MATCH id='q39-44'>\r
+        <BODY>\r
+            <TEXT><![CDATA[<i>Classify the animals.</i>]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q39-44_a1' position='1'>\r
+            <TEXT><![CDATA[frog]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q39-44_a2' position='2'>\r
+            <TEXT><![CDATA[cat]]></TEXT>\r
+        </ANSWER>\r
+        <ANSWER id='q39-44_a3' position='3'>\r
+            <TEXT><![CDATA[newt]]></TEXT>\r
+        </ANSWER>\r
+        <CHOICE id='q39-44_c1' position='1'>\r
+            <TEXT><![CDATA[mammal]]></TEXT>\r
+        </CHOICE>\r
+        <CHOICE id='q39-44_c2' position='2'>\r
+            <TEXT><![CDATA[insect]]></TEXT>\r
+        </CHOICE>\r
+        <CHOICE id='q39-44_c3' position='3'>\r
+            <TEXT><![CDATA[amphibian]]></TEXT>\r
+        </CHOICE>\r
+        <GRADABLE>\r
+            <CORRECTANSWER answer_id='q39-44_a1' choice_id='q39-44_c3'/>\r
+            <CORRECTANSWER answer_id='q39-44_a2' choice_id='q39-44_c1'/>\r
+            <CORRECTANSWER answer_id='q39-44_a3' choice_id='q39-44_c3'/>\r
+        </GRADABLE>\r
+    </QUESTION_MATCH>\r
+    <QUESTION_ESSAY id='q9'>\r
+        <BODY>\r
+            <TEXT><![CDATA[How are you?]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q9_a1'>\r
+            <TEXT><![CDATA[Blackboard answer for essay questions will be imported as informations for graders.]]></TEXT>\r
+        </ANSWER>\r
+        <GRADABLE>\r
+        </GRADABLE>\r
+    </QUESTION_ESSAY>\r
+    <QUESTION_FILLINBLANK id='q27'>\r
+        <BODY>\r
+            <TEXT><![CDATA[<span style="font-size:12pt">Name an amphibian: __________.</span>]]></TEXT>\r
+            <FLAGS>\r
+                <ISHTML value='true'/>\r
+                <ISNEWLINELITERAL value='false'/>\r
+            </FLAGS>\r
+        </BODY>\r
+        <ANSWER id='q27_a1' position='1'>\r
+            <TEXT>frog</TEXT>\r
+        </ANSWER>\r
+        <GRADABLE>\r
+        </GRADABLE>\r
+    </QUESTION_FILLINBLANK>\r
+</POOL>\r
diff --git a/question/format/blackboard_six/tests/fixtures/sample_blackboard_qti.dat b/question/format/blackboard_six/tests/fixtures/sample_blackboard_qti.dat
new file mode 100644 (file)
index 0000000..4705c2b
--- /dev/null
@@ -0,0 +1,1058 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<questestinterop>
+    <assessment title="sample_blackboard_six">
+        <section>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>E03EE0034702442C9306629CBF618049</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>True/False</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,1.0</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>1.0</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;42 is the Absolute Answer to everything.&lt;/span&gt;</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <response_lid ident="response" rcardinality="Single" rtiming="No">
+                                <render_choice maxnumber="0" minnumber="0" shuffle="No">
+                                    <flow_label class="Block">
+                                        <response_label ident="true" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="Block">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" xml:space="default">true</mattext>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                        <response_label ident="false" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="Block">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" xml:space="default">false</mattext>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                </render_choice>
+                            </response_lid>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="1.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition title="correct">
+                        <conditionvar>
+                            <varequal case="No" respident="response">false</varequal>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">SCORE.max</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML">You gave the right answer.</mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML">42 is the &lt;b&gt;Ultimate&lt;/b&gt; Answer.</mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+            </item>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>C74698725FFD4F85A692662108608D53</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>Multiple Choice</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,1.0</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>1.0</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;What's between orange and green in the spectrum?&lt;/span&gt;</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <response_lid ident="response" rcardinality="Single" rtiming="No">
+                                <render_choice maxnumber="0" minnumber="0" shuffle="Yes">
+                                    <flow_label class="Block">
+                                        <response_label ident="7C2A0246CE8D46599FC0120BAE9FC92D" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;red&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                    <flow_label class="Block">
+                                        <response_label ident="2CBE1E044DE54F8395BDE7877A57837A" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;yellow&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                    <flow_label class="Block">
+                                        <response_label ident="67A8748A0883467FB45328E922C31D29" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;blue&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                </render_choice>
+                            </response_lid>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="1.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition title="correct">
+                        <conditionvar>
+                            <varequal case="No" respident="response">2CBE1E044DE54F8395BDE7877A57837A</varequal>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">SCORE.max</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="7C2A0246CE8D46599FC0120BAE9FC92D" view="All">
+                    <solution feedbackstyle="Complete" view="All">
+                        <solutionmaterial>
+                            <flow_mat class="Block">
+                                <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">Red is not between orange and green in the spectrum but yellow is.</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow_mat>
+                                <flow_mat class="FILE_BLOCK">
+                                    <material/>
+                                </flow_mat>
+                                <flow_mat class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow_mat>
+                            </flow_mat>
+                        </solutionmaterial>
+                    </solution>
+                </itemfeedback>
+                <itemfeedback ident="2CBE1E044DE54F8395BDE7877A57837A" view="All">
+                    <solution feedbackstyle="Complete" view="All">
+                        <solutionmaterial>
+                            <flow_mat class="Block">
+                                <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">You gave the right answer.</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow_mat>
+                                <flow_mat class="FILE_BLOCK">
+                                    <material/>
+                                </flow_mat>
+                                <flow_mat class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow_mat>
+                            </flow_mat>
+                        </solutionmaterial>
+                    </solution>
+                </itemfeedback>
+                <itemfeedback ident="67A8748A0883467FB45328E922C31D29" view="All">
+                    <solution feedbackstyle="Complete" view="All">
+                        <solutionmaterial>
+                            <flow_mat class="Block">
+                                <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">Blue is not between orange and green in the spectrum but yellow is.</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow_mat>
+                                <flow_mat class="FILE_BLOCK">
+                                    <material/>
+                                </flow_mat>
+                                <flow_mat class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow_mat>
+                            </flow_mat>
+                        </solutionmaterial>
+                    </solution>
+                </itemfeedback>
+            </item>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>C18C56154AA04D56A4AE2FE430F4F49D</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>Multiple Answer</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,1.0</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>1.0</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">&lt;i&gt;What's between orange and green in the spectrum?&lt;/i&gt;</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <response_lid ident="response" rcardinality="Multiple" rtiming="No">
+                                <render_choice maxnumber="0" minnumber="0" shuffle="Yes">
+                                    <flow_label class="Block">
+                                        <response_label ident="76CA08C366984445AC94B0244D1DBF4A" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;yellow&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                    <flow_label class="Block">
+                                        <response_label ident="FEC2A9886C8B498787A573C9181C9698" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;red&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                    <flow_label class="Block">
+                                        <response_label ident="7F66D24D2CAA472EA728773D46706DF3" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;off-beige&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                    <flow_label class="Block">
+                                        <response_label ident="547B16C1D788446396618EDD0A41D623" rarea="Ellipse" rrange="Exact" shuffle="Yes">
+                                            <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                                <material>
+                                                    <mat_extension>
+                                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;blue&lt;/span&gt;</mat_formattedtext>
+                                                    </mat_extension>
+                                                </material>
+                                            </flow_mat>
+                                            <flow_mat class="FILE_BLOCK">
+                                                <material/>
+                                            </flow_mat>
+                                            <flow_mat class="LINK_BLOCK">
+                                                <material>
+                                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                                </material>
+                                            </flow_mat>
+                                        </response_label>
+                                    </flow_label>
+                                </render_choice>
+                            </response_lid>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="1.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition title="correct">
+                        <conditionvar>
+                            <and>
+                                <varequal case="No" respident="response">76CA08C366984445AC94B0244D1DBF4A</varequal>
+                                <not>
+                                    <varequal case="No" respident="response">FEC2A9886C8B498787A573C9181C9698</varequal>
+                                </not>
+                                <varequal case="No" respident="response">7F66D24D2CAA472EA728773D46706DF3</varequal>
+                                <not>
+                                    <varequal case="No" respident="response">547B16C1D788446396618EDD0A41D623</varequal>
+                                </not>
+                            </and>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">SCORE.max</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+            </item>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>9C5DBA6A142A4B5887C61D333CFFEDA9</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>Matching</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,3</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>3</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">Classify the animals.</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">cat</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                                <response_lid ident="6D3235200B3F43DFA8FA13E2B31BB40B" rcardinality="Single" rtiming="No">
+                                    <render_choice maxnumber="0" minnumber="0" shuffle="Yes">
+                                        <flow_label class="Block">
+                                            <response_label ident="2F591AA030B240EF869FD56392FC41BC" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="D75FEB705DCE41D59106659A2F94D819" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="207B18A11C4B42BF87882A4BAF3CC805" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                        </flow_label>
+                                    </render_choice>
+                                </response_lid>
+                            </flow>
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">frog</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                                <response_lid ident="0933892218204F5AB561E62A27701447" rcardinality="Single" rtiming="No">
+                                    <render_choice maxnumber="0" minnumber="0" shuffle="Yes">
+                                        <flow_label class="Block">
+                                            <response_label ident="2C1FB19B5F9A4F7A85E798B9C46B8BF8" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="0EEF502254D2496FB21FFD82B0A7F2B9" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="8A9B69A93B0943AFAB890702199AB290" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                        </flow_label>
+                                    </render_choice>
+                                </response_lid>
+                            </flow>
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">newt</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                                <response_lid ident="80F56B540A44490B8E94EC71C4584722" rcardinality="Single" rtiming="No">
+                                    <render_choice maxnumber="0" minnumber="0" shuffle="Yes">
+                                        <flow_label class="Block">
+                                            <response_label ident="1D066C2DAEF349EB8E7845B339B0A4A9" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="F2FB88DFE0D04DBEBD42B961728CA022" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                            <response_label ident="4D1F5B3DB0EB4C41A0012625750DF86C" rarea="Ellipse" rrange="Exact" shuffle="Yes"/>
+                                        </flow_label>
+                                    </render_choice>
+                                </response_lid>
+                            </flow>
+                        </flow>
+                        <flow class="RIGHT_MATCH_BLOCK">
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">insect</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                            </flow>
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">mammal</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                            </flow>
+                            <flow class="Block">
+                                <flow class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">amphibian</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow>
+                                <flow class="FILE_BLOCK">
+                                    <material/>
+                                </flow>
+                                <flow class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow>
+                            </flow>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="3.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition>
+                        <conditionvar>
+                            <varequal case="No" respident="6D3235200B3F43DFA8FA13E2B31BB40B">D75FEB705DCE41D59106659A2F94D819</varequal>
+                        </conditionvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition>
+                        <conditionvar>
+                            <varequal case="No" respident="0933892218204F5AB561E62A27701447">8A9B69A93B0943AFAB890702199AB290</varequal>
+                        </conditionvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition>
+                        <conditionvar>
+                            <varequal case="No" respident="80F56B540A44490B8E94EC71C4584722">4D1F5B3DB0EB4C41A0012625750DF86C</varequal>
+                        </conditionvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+            </item>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>DD76E663D4244C598FC91CFC433F6D5B</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>Fill in the Blank</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,1.0</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>1.0</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">&lt;span style="font-size:12pt"&gt;Name an amphibian&amp;#58; __________.&lt;/span&gt;</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <response_str ident="response" rcardinality="Single" rtiming="No">
+                                <render_fib charset="us-ascii" columns="127" encoding="UTF_8" fibtype="String" maxchars="0" maxnumber="0" minnumber="0" prompt="Box" rows="1"/>
+                            </response_str>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="1.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition title="1CE934E53BDB437B8FD315E68063DA47">
+                        <conditionvar>
+                            <varequal case="No" respident="response">frog</varequal>
+                        </conditionvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                        <displayfeedback feedbacktype="Response" linkrefid="1CE934E53BDB437B8FD315E68063DA47"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML">A frog is an amphibian.</mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML">A frog is an amphibian.</mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="1CE934E53BDB437B8FD315E68063DA47" view="All">
+                    <solution feedbackstyle="Complete" view="All">
+                        <solutionmaterial>
+                            <flow_mat class="Block">
+                                <flow_mat class="FORMATTED_TEXT_BLOCK">
+                                    <material>
+                                        <mat_extension>
+                                            <mat_formattedtext type="HTML">A frog is an amphibian.</mat_formattedtext>
+                                        </mat_extension>
+                                    </material>
+                                </flow_mat>
+                                <flow_mat class="FILE_BLOCK">
+                                    <material/>
+                                </flow_mat>
+                                <flow_mat class="LINK_BLOCK">
+                                    <material>
+                                        <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                    </material>
+                                </flow_mat>
+                            </flow_mat>
+                        </solutionmaterial>
+                    </solution>
+                </itemfeedback>
+            </item>
+            <item maxattempts="0">
+                <itemmetadata>
+                    <bbmd_asi_object_id>39970AD8D5AE425A82338E17D42B7845</bbmd_asi_object_id>
+                    <bbmd_asitype>Item</bbmd_asitype>
+                    <bbmd_assessmenttype>Pool</bbmd_assessmenttype>
+                    <bbmd_sectiontype>Subsection</bbmd_sectiontype>
+                    <bbmd_questiontype>Essay</bbmd_questiontype>
+                    <bbmd_is_from_cartridge>false</bbmd_is_from_cartridge>
+                    <qmd_absolutescore>0.0,1.0</qmd_absolutescore>
+                    <qmd_absolutescore_min>0.0</qmd_absolutescore_min>
+                    <qmd_absolutescore_max>1.0</qmd_absolutescore_max>
+                    <qmd_assessmenttype>Proprietary</qmd_assessmenttype>
+                    <qmd_itemtype>Logical Identifier</qmd_itemtype>
+                    <qmd_levelofdifficulty>School</qmd_levelofdifficulty>
+                    <qmd_maximumscore>0.0</qmd_maximumscore>
+                    <qmd_numberofitems>0</qmd_numberofitems>
+                    <qmd_renderingtype>Proprietary</qmd_renderingtype>
+                    <qmd_responsetype>Single</qmd_responsetype>
+                    <qmd_scoretype>Absolute</qmd_scoretype>
+                    <qmd_status>Normal</qmd_status>
+                    <qmd_timelimit>0</qmd_timelimit>
+                    <qmd_weighting>0.0</qmd_weighting>
+                    <qmd_typeofsolution>Complete</qmd_typeofsolution>
+                </itemmetadata>
+                <presentation>
+                    <flow class="Block">
+                        <flow class="QUESTION_BLOCK">
+                            <flow class="FORMATTED_TEXT_BLOCK">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">How are you?</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow>
+                            <flow class="FILE_BLOCK">
+                                <material/>
+                            </flow>
+                            <flow class="LINK_BLOCK">
+                                <material>
+                                    <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                                </material>
+                            </flow>
+                        </flow>
+                        <flow class="RESPONSE_BLOCK">
+                            <response_str ident="response" rcardinality="Single" rtiming="No">
+                                <render_fib charset="us-ascii" columns="127" encoding="UTF_8" fibtype="String" maxchars="0" maxnumber="0" minnumber="0" prompt="Box" rows="8"/>
+                            </response_str>
+                        </flow>
+                    </flow>
+                </presentation>
+                <resprocessing scoremodel="SumOfScores">
+                    <outcomes>
+                        <decvar defaultval="0.0" maxvalue="1.0" minvalue="0.0" varname="SCORE" vartype="Decimal"/>
+                    </outcomes>
+                    <respcondition title="correct">
+                        <conditionvar/>
+                        <setvar action="Set" variablename="SCORE">SCORE.max</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="correct"/>
+                    </respcondition>
+                    <respcondition title="incorrect">
+                        <conditionvar>
+                            <other/>
+                        </conditionvar>
+                        <setvar action="Set" variablename="SCORE">0.0</setvar>
+                        <displayfeedback feedbacktype="Response" linkrefid="incorrect"/>
+                    </respcondition>
+                </resprocessing>
+                <itemfeedback ident="correct" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="incorrect" view="All">
+                    <flow_mat class="Block">
+                        <flow_mat class="FORMATTED_TEXT_BLOCK">
+                            <material>
+                                <mat_extension>
+                                    <mat_formattedtext type="HTML"></mat_formattedtext>
+                                </mat_extension>
+                            </material>
+                        </flow_mat>
+                        <flow_mat class="FILE_BLOCK">
+                            <material/>
+                        </flow_mat>
+                        <flow_mat class="LINK_BLOCK">
+                            <material>
+                                <mattext charset="us-ascii" texttype="text/plain" uri="" xml:space="default"/>
+                            </material>
+                        </flow_mat>
+                    </flow_mat>
+                </itemfeedback>
+                <itemfeedback ident="solution" view="All">
+                    <solution feedbackstyle="Complete" view="All">
+                        <solutionmaterial>
+                            <flow_mat class="Block">
+                                <material>
+                                    <mat_extension>
+                                        <mat_formattedtext type="HTML">Blackboard answer for essay questions will be imported as informations for graders.</mat_formattedtext>
+                                    </mat_extension>
+                                </material>
+                            </flow_mat>
+                        </solutionmaterial>
+                    </solution>
+                </itemfeedback>
+            </item>
+        </section>
+    </assessment>
+</questestinterop>
index c037b03..c413d77 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Version information for the calculated question type.
+ * Version information for the blackboard_six question import format.
  *
- * @package    qformat
- * @subpackage blackboard_six
+ * @package    qformat_blackboard_six
  * @copyright  2011 The Open University
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 7cea482..9cb9bb5 100644 (file)
@@ -61,28 +61,6 @@ class qformat_examview extends qformat_based_on_xml {
         return 'application/xml';
     }
 
-    /**
-     * Some softwares put entities in exported files.
-     * This method try to clean up known problems.
-     * @param string str string to correct
-     * @return string the corrected string
-     */
-    public function cleaninput($str) {
-
-        $html_code_list = array(
-            "&#039;" => "'",
-            "&#8217;" => "'",
-            "&#8220;" => "\"",
-            "&#8221;" => "\"",
-            "&#8211;" => "-",
-            "&#8212;" => "-",
-        );
-        $str = strtr($str, $html_code_list);
-        // Use textlib entities_to_utf8 function to convert only numerical entities.
-        $str = textlib::entities_to_utf8( $str, false);
-        return $str;
-    }
-
     /**
      * unxmlise reconstructs part of the xml data structure in order
      * to identify the actual data therein
@@ -109,19 +87,6 @@ class qformat_examview extends qformat_based_on_xml {
         return $text;
     }
 
-    protected function add_blank_combined_feedback($question) {
-        $question->correctfeedback['text'] = '';
-        $question->correctfeedback['format'] = $question->questiontextformat;
-        $question->correctfeedback['files'] = array();
-        $question->partiallycorrectfeedback['text'] = '';
-        $question->partiallycorrectfeedback['format'] = $question->questiontextformat;
-        $question->partiallycorrectfeedback['files'] = array();
-        $question->incorrectfeedback['text'] = '';
-        $question->incorrectfeedback['format'] = $question->questiontextformat;
-        $question->incorrectfeedback['files'] = array();
-        return $question;
-    }
-
     public function parse_matching_groups($matching_groups) {
         if (empty($matching_groups)) {
             return;
index 06bcebc..1cb0e25 100644 (file)
@@ -537,19 +537,6 @@ class qformat_gift extends qformat_default {
         }
     }
 
-    protected function add_blank_combined_feedback($question) {
-        $question->correctfeedback['text'] = '';
-        $question->correctfeedback['format'] = $question->questiontextformat;
-        $question->correctfeedback['files'] = array();
-        $question->partiallycorrectfeedback['text'] = '';
-        $question->partiallycorrectfeedback['format'] = $question->questiontextformat;
-        $question->partiallycorrectfeedback['files'] = array();
-        $question->incorrectfeedback['text'] = '';
-        $question->incorrectfeedback['format'] = $question->questiontextformat;
-        $question->incorrectfeedback['files'] = array();
-        return $question;
-    }
-
     protected function repchar($text, $notused = 0) {
         // Escapes 'reserved' characters # = ~ {) :
         // Removes new lines