]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-14431:
authorthepurpleblob <thepurpleblob>
Fri, 11 Jul 2008 10:49:08 +0000 (10:49 +0000)
committerthepurpleblob <thepurpleblob>
Fri, 11 Jul 2008 10:49:08 +0000 (10:49 +0000)
Moving qti2 to qti_two as numbers are not allowed in module names

17 files changed:
lang/en_utf8/help/qformat_qti_two/qti_two.html [moved from lang/en_utf8/help/qformat_qti2/qti2.html with 100% similarity]
lang/en_utf8/quiz.php
question/format/qti_two/custommediafilter.php [new file with mode: 0644]
question/format/qti_two/format.php [new file with mode: 0644]
question/format/qti_two/qt_common.php [new file with mode: 0644]
question/format/qti_two/templates/choice.tpl [new file with mode: 0755]
question/format/qti_two/templates/choiceMultiple.tpl [new file with mode: 0755]
question/format/qti_two/templates/composite.tpl [new file with mode: 0755]
question/format/qti_two/templates/extendedText.tpl [new file with mode: 0755]
question/format/qti_two/templates/extendedText_simpleEssay.tpl [new file with mode: 0755]
question/format/qti_two/templates/graphicGapMatch.tpl [new file with mode: 0644]
question/format/qti_two/templates/imsmanifest.tpl [new file with mode: 0755]
question/format/qti_two/templates/match.tpl [new file with mode: 0644]
question/format/qti_two/templates/mmchoiceMultiple.tpl [new file with mode: 0755]
question/format/qti_two/templates/notimplemented.tpl [new file with mode: 0755]
question/format/qti_two/templates/numerical.tpl [new file with mode: 0755]
question/format/qti_two/templates/textEntry.tpl [new file with mode: 0755]

index 9a6fa1ea1b9ece3de168fc77d3305c6e2b6bbc93..e3aefdec6c3fe1b4ca7cb7a5205e02e423b7bf5e 100644 (file)
@@ -430,7 +430,7 @@ $string['previous'] = 'Previous state';
 $string['publish'] = 'Publish';
 $string['publishedit'] = 'You must have permission in the publishing course to add or edit questions in this category';
 $string['qti'] = 'IMS QTI format';
-$string['qti2'] = 'IMS QTI 2.0 format';
+$string['qti_two'] = 'IMS QTI 2.0 format';
 $string['question'] = 'Question';
 $string['questioncats'] = 'Question Categories';
 $string['questiondeleted'] = 'This question has been deleted. Please contact your teacher';
diff --git a/question/format/qti_two/custommediafilter.php b/question/format/qti_two/custommediafilter.php
new file mode 100644 (file)
index 0000000..c7fcba8
--- /dev/null
@@ -0,0 +1,150 @@
+<?php // $id$
+
+// note: modified from the original filter/mediaplugin/filter.php
+
+// given a href for a movie or sound file, it returns an appropriate tag
+/**
+ * @package questionbank
+ * @subpackage importexport
+ */
+function custom_mediaplugin_filter($text, $width = null, $height = null) {
+    global $CFG;
+    if (is_null($width) || $width == 0) {
+        $usedefaults = true;
+        $width = 400;
+        $height = 300;
+    } else {
+        $usedefaults = false;
+    }
+
+    if (empty($CFG->filter_mediaplugin_ignore_mp3)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.mp3\"([^>]*)>(.*?)<\/a>/i';
+
+        $replace  = '\\0&nbsp;<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
+        $replace .= ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" ';
+        $replace .= ' width="35" height="18" id="mp3player" align="">';
+        $replace .= " <param name=movie value=\"$CFG->wwwroot/filter/mediaplugin/mp3player.swf?src=\\2.mp3\">";
+        $replace .= ' <param name=quality value=high>';
+        $replace .= ' <param name=bgcolor value="#333333">';
+        $replace .= " <embed src=\"$CFG->wwwroot/filter/mediaplugin/mp3player.swf?src=\\2.mp3\" ";
+        $replace .= "  quality=high bgcolor=\"#333333\" width=\"35\" height=\"18\" name=\"mp3player\" ";
+        $replace .= ' type="application/x-shockwave-flash" ';
+        $replace .= ' pluginspage="http://www.macromedia.com/go/getflashplayer">';
+        $replace .= '</embed>';
+        $replace .= '</object>&nbsp;';
+    
+        $text = preg_replace($search, $replace, $text);
+    }
+
+    if (empty($CFG->filter_mediaplugin_ignore_swf)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.swf\"([^>]*)>(.*?)<\/a>/i';
+        $replace  = '\\0<object '.
+                            'type="application/x-shockwave-flash" ' .
+                            'data="\\2.swf" ' .
+                            'width="' . $width . '" ' .
+                            'height="' . $height . '"> ' .
+                        '<param name="movie" value="\\2.swf" /> ' .
+                        '<param name="wmode" value="transparent" />' .
+                        '</object>';
+
+
+/*        $replace  = '\\0<p class="mediaplugin"><object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
+        $replace .= ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0" ';
+        $replace .= ' width="' . $width . '" height="' . $height . '" id="mp3player" align="">';
+        $replace .= " <param name=movie value=\"\\2.swf\">";
+        $replace .= ' <param name=quality value=high>';
+        $replace .= " <embed src=\"\\2.swf\" ";
+        $replace .= "  quality=high width=\"$width\" height=\"$height\" name=\"flashfilter\" ";
+        $replace .= ' type="application/x-shockwave-flash" ';
+        $replace .= ' pluginspage="http://www.macromedia.com/go/getflashplayer">';
+        $replace .= '</embed>';
+        $replace .= '</object></p>';*/
+    
+        $text = preg_replace($search, $replace, $text);
+    }
+
+    if (empty($CFG->filter_mediaplugin_ignore_mov)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.mov\"([^>]*)>(.*?)<\/a>/i';
+
+        $replace  = '\\0<p class="mediaplugin"><object classid="CLSID:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"';
+        $replace .= '        codebase="http://www.apple.com/qtactivex/qtplugin.cab" ';
+        $replace .= '        height="' . $height . '" width="' . $width . '"';
+        $replace .= '        id="quicktime" align="" type="application/x-oleobject">';
+        $replace .= "<param name=\"src\" value=\"\\2.mov\" />";
+        $replace .= '<param name="autoplay" value=false />';
+        $replace .= '<param name="loop" value=true />';
+        $replace .= '<param name="controller" value=true />';
+        $replace .= '<param name="scale" value="aspect" />';
+        $replace .= "\n<embed src=\"\\2.mov\" name=\"quicktime\" type=\"video/quicktime\" ";
+        $replace .= ' height="' . $height . '" width="' . $width . '" scale="aspect" ';
+        $replace .= ' autoplay="false" controller="true" loop="true" ';
+        $replace .= ' pluginspage="http://quicktime.apple.com/">';
+        $replace .= '</embed>';
+        $replace .= '</object>&nbsp;';
+
+        $text = preg_replace($search, $replace, $text);
+    }
+
+    if (empty($CFG->filter_mediaplugin_ignore_wmv)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.wmv\"([^>]*)>(.*?)<\/a>/i';
+
+        $replace  = '\\0<p class="mediaplugin"><object classid="CLSID:22D6f312-B0F6-11D0-94AB-0080C74C7E95"';
+        $replace .= ' codebase="http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701" ';
+        $replace .= ' standby="Loading Microsoft? Windows? Media Player components..." ';
+        $replace .= ' id="msplayer" align="" type="application/x-oleobject">';
+        $replace .= "<param name=\"Filename\" value=\"\\2.wmv\">";
+        $replace .= '<param name="ShowControls" value=true />';
+        $replace .= '<param name="AutoRewind" value=true />';
+        $replace .= '<param name="AutoStart" value=false />';
+        $replace .= '<param name="Autosize" value=true />';
+        $replace .= '<param name="EnableContextMenu" value=true />';
+        $replace .= '<param name="TransparentAtStart" value=false />';
+        $replace .= '<param name="AnimationAtStart" value=false />';
+        $replace .= '<param name="ShowGotoBar" value=false />';
+        $replace .= '<param name="EnableFullScreenControls" value=true />';
+        $replace .= "\n<embed src=\"\\2.wmv\" name=\"msplayer\" type=\"video/x-ms\" ";
+        $replace .= ' ShowControls="1" AutoRewind="1" AutoStart="0" Autosize="0" EnableContextMenu="1"';
+        $replace .= ' TransparentAtStart="0" AnimationAtStart="0" ShowGotoBar="0" EnableFullScreenControls="1"';
+        $replace .= ' pluginspage="http://www.microsoft.com/Windows/Downloads/Contents/Products/MediaPlayer/">';
+        $replace .= '</embed>';
+        $replace .= '</object>&nbsp;';
+
+        $text = preg_replace($search, $replace, $text);
+    }
+
+    if ($usedefaults) {
+        $width = 240;
+        $height = 180;
+    }
+    
+    if (empty($CFG->filter_mediaplugin_ignore_mpg)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.(mpe?g)\"([^>]*)>(.*?)<\/a>/i';
+
+        $replace = '\\0<p class="mediaplugin"><object width="' . $width . '" height="' . $height . '">';
+        $replace .= '<param name="src" value="\\2.\\3">';
+        $replace .= '<param name="controller" value="true">';
+        $replace .= '<param name="autoplay" value="false">';
+        $replace .= '<embed src="\\2.\\3" width="' . $width . '" height="' . $height . '" controller="true" autoplay="false"> </embed>';
+        $replace .= '</object></p>';
+        
+        $text = preg_replace($search, $replace, $text);
+    }
+
+    if (empty($CFG->filter_mediaplugin_ignore_avi)) {
+        $search = '/<a(.*?)href=\"([^<]+)\.avi\"([^>]*)>(.*?)<\/a>/i';
+
+        $replace = '\\0<p class="mediaplugin"><object width="' . $width . '" height="' . $height . '">';
+        $replace .= '<param name="src" value="\\2.avi">';
+        $replace .= '<param name="controller" value="true">';
+        $replace .= '<param name="autoplay" value="false">';
+        $replace .= '<embed src="\\2.avi" width="' . $width . '" height="' . $height . '" controller="true" autoplay="false"> </embed>';
+        $replace .= '</object>&nbsp;';
+    
+        $text = preg_replace($search, $replace, $text);
+    }
+    return $text;
+}
+
+
+?>
+
diff --git a/question/format/qti_two/format.php b/question/format/qti_two/format.php
new file mode 100644 (file)
index 0000000..8e01eb7
--- /dev/null
@@ -0,0 +1,921 @@
+<?php  // $Id$
+
+require_once("$CFG->dirroot/question/format/qti_two/qt_common.php");
+////////////////////////////////////////////////////////////////////////////
+/// IMS QTI 2.0 FORMAT
+///
+/// HISTORY: created 28.01.2005      brian@mediagonal.ch
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+/**
+ * @package questionbank
+ * @subpackage importexport
+ */
+define('CLOZE_TRAILING_TEXT_ID', 9999999);
+
+class qformat_qti_two extends qformat_default {
+    
+    var $lang;
+
+    function provide_export() {
+       return true;
+    }
+    
+    function indent_xhtml($source, $indenter = ' ') { 
+        // xml tidier-upper
+        // (c) Ari Koivula http://ventionline.com
+        
+        // Remove all pre-existing formatting. 
+        // Remove all newlines. 
+        $source = str_replace("\n", '', $source); 
+        $source = str_replace("\r", '', $source); 
+        // Remove all tabs. 
+        $source = str_replace("\t", '', $source); 
+        // Remove all space after ">" and before "<". 
+        $source = ereg_replace(">( )*", ">", $source); 
+        $source = ereg_replace("( )*<", "<", $source); 
+    
+        // Iterate through the source. 
+        $level = 0; 
+        $source_len = strlen($source); 
+        $pt = 0; 
+        while ($pt < $source_len) { 
+            if ($source{$pt} === '<') { 
+                // We have entered a tag. 
+                // Remember the point where the tag starts. 
+                $started_at = $pt; 
+                $tag_level = 1; 
+                // If the second letter of the tag is "/", assume its an ending tag. 
+                if ($source{$pt+1} === '/') { 
+                    $tag_level = -1; 
+                } 
+                // If the second letter of the tag is "!", assume its an "invisible" tag. 
+                if ($source{$pt+1} === '!') { 
+                    $tag_level = 0; 
+                } 
+                // Iterate throught the source until the end of tag. 
+                while ($source{$pt} !== '>') { 
+                    $pt++; 
+                } 
+                // If the second last letter is "/", assume its a self ending tag. 
+                if ($source{$pt-1} === '/') { 
+                    $tag_level = 0; 
+                } 
+                $tag_lenght = $pt+1-$started_at; 
+                 
+                // Decide the level of indention for this tag. 
+                // If this was an ending tag, decrease indent level for this tag.. 
+                if ($tag_level === -1) { 
+                    $level--; 
+                } 
+                // Place the tag in an array with proper indention. 
+                $array[] = str_repeat($indenter, $level).substr($source, $started_at, $tag_lenght); 
+                // If this was a starting tag, increase the indent level after this tag. 
+                if ($tag_level === 1) { 
+                    $level++; 
+                } 
+                // if it was a self closing tag, dont do shit. 
+            } 
+            // Were out of the tag. 
+            // If next letter exists... 
+            if (($pt+1) < $source_len) { 
+                // ... and its not an "<". 
+                if ($source{$pt+1} !== '<') { 
+                    $started_at = $pt+1; 
+                    // Iterate through the source until the start of new tag or until we reach the end of file. 
+                    while ($source{$pt} !== '<' && $pt < $source_len) { 
+                        $pt++; 
+                    } 
+                    // If we found a "<" (we didnt find the end of file) 
+                    if ($source{$pt} === '<') { 
+                        $tag_lenght = $pt-$started_at; 
+                        // Place the stuff in an array with proper indention. 
+                        $array[] = str_repeat($indenter, $level).substr($source, $started_at, $tag_lenght); 
+                    } 
+                // If the next tag is "<", just advance pointer and let the tag indenter take care of it. 
+                } else { 
+                    $pt++; 
+                } 
+            // If the next letter doesnt exist... Were done... well, almost.. 
+            } else { 
+                break; 
+            } 
+        } 
+        // Replace old source with the new one we just collected into our array. 
+        $source = implode($array, "\n"); 
+        return $source; 
+    } 
+    
+    function importpreprocess() {
+        global $CFG;
+
+        error("Sorry, importing this format is not yet implemented!", 
+            "$CFG->wwwroot/mod/quiz/import.php?category=$category->id");
+    }
+
+    function exportpreprocess() {
+        global $CFG;
+        
+        require_once("{$CFG->libdir}/smarty/Smarty.class.php");
+        
+        // assign the language for the export: by parameter, SESSION, USER, or the default of 'en'
+        $lang = current_language();
+        $this->lang = $lang;
+        
+        return parent::exportpreprocess();
+    }
+    
+    
+    function export_file_extension() {
+        // override default type so extension is .xml
+        
+        return ".zip";
+    }
+    
+    function get_qtype( $type_id ) {
+        // translates question type code number into actual name
+       
+        switch( $type_id ) {
+        case TRUEFALSE:
+            $name = 'truefalse';
+            break;
+        case MULTICHOICE:
+            $name = 'multichoice';
+            break;
+        case SHORTANSWER:
+            $name = 'shortanswer';
+            break;
+        case NUMERICAL:
+            $name = 'numerical';
+            break;
+        case MATCH:
+            $name = 'matching';
+            break;
+        case DESCRIPTION:
+            $name = 'description';
+            break;
+        case MULTIANSWER:
+            $name = 'multianswer';
+            break;
+        default:
+            $name = 'Unknown';
+        }
+        return $name;
+    }
+
+    function writetext( $raw ) {
+        // generates <text></text> tags, processing raw text therein 
+    
+        // for now, don't allow any additional tags in text 
+        // otherwise xml rules would probably get broken
+        $raw = strip_tags( $raw );
+    
+        return "<text>$raw</text>\n";
+    }
+    
+
+/**
+ * flattens $object['media'], copies $object['media'] to $path, and sets $object['mediamimetype']
+ *
+ * @param array &$object containing a field 'media'
+ * @param string $path the full path name to where the media files need to be copied
+ * @param int $courseid
+ * @return: mixed - true on success or in case of an empty media field, an error string if the file copy fails
+ */    
+function copy_and_flatten(&$object, $path, $courseid) {
+    global $CFG;
+    if (!empty($object['media'])) {
+        $location = $object['media'];
+        $object['media'] = $this->flatten_image_name($location);
+        if (!@copy("{$CFG->dataroot}/$courseid/$location", "$path/{$object['media']}")) {
+            return "Failed to copy {$CFG->dataroot}/$courseid/$location to $path/{$object['media']}";
+        }
+        if (empty($object['mediamimetype'])) {
+            $object['mediamimetype'] = mimeinfo('type', $object['media']);
+        }
+    }
+    return true;
+} 
+/**
+ * copies all files needed by the questions to the given $path, and flattens the file names
+ *
+ * @param array $questions the question objects
+ * @param string $path the full path name to where the media files need to be copied
+ * @param int $courseid
+ * @return mixed true on success, an array of error messages otherwise
+ */    
+function handle_questions_media(&$questions, $path, $courseid) {
+    global $CFG;
+    $errors = array();
+    foreach ($questions as $key=>$question) {
+        
+    // todo: handle in-line media (specified in the question text)
+        if (!empty($question->image)) {
+            $location = $questions[$key]->image;
+            $questions[$key]->mediaurl = $this->flatten_image_name($location);
+            if (!@copy("{$CFG->dataroot}/$courseid/$location", "$path/{$questions[$key]->mediaurl}")) {
+                $errors[] = "Failed to copy {$CFG->dataroot}/$courseid/$location to $path/{$questions[$key]->mediaurl}";
+            }
+            if (empty($question->mediamimetype)) {
+                $questions[$key]->mediamimetype = mimeinfo('type', $question->image);
+            }
+        }
+    }
+    
+    return empty($errors) ? true : $errors;
+}
+
+/**
+ * exports the questions in a question category to the given location
+ *
+ * The parent class method was overridden because the IMS export consists of multiple files
+ *
+ * @param string $filename the directory name which will hold the exported files
+ * @return boolean - or errors out
+ */    
+    function exportprocess() {
+
+        global $CFG;
+        $courseid = $this->course->id;
+
+        // create a directory for the exports (if not already existing)
+        if (!$export_dir = make_upload_directory($this->question_get_export_dir().'/'.$this->filename)) {
+              error( get_string('cannotcreatepath','quiz',$export_dir) );
+        }
+        $path = $CFG->dataroot.'/'.$this->question_get_export_dir().'/'.$this->filename;
+
+        // get the questions (from database) in this category
+        // $questions = get_records("question","category",$this->category->id);
+        $questions = get_questions_category( $this->category );
+        
+        notify("Exporting ".count($questions)." questions.");
+        $count = 0;
+
+        // create the imsmanifest file
+        $smarty =& $this->init_smarty();
+        $this->add_qti_info($questions);
+        
+        // copy files used by the main questions to the export directory
+        $result = $this->handle_questions_media($questions, $path, $courseid);
+        if ($result !== true) {
+            notify(implode("<br />", $result));
+        }
+       
+        $manifestquestions = $this->objects_to_array($questions);
+        $manifestid = str_replace(array(':', '/'), array('-','_'), "question_category_{$this->category->id}---{$CFG->wwwroot}");
+        $smarty->assign('externalfiles', 1);
+        $smarty->assign('manifestidentifier', $manifestid);
+        $smarty->assign('quiztitle', "question_category_{$this->category->id}");
+        $smarty->assign('quizinfo', "All questions in category {$this->category->id}");
+        $smarty->assign('questions', $manifestquestions);
+        $smarty->assign('lang', $this->lang);
+        $smarty->error_reporting = 99;
+        $expout = $smarty->fetch('imsmanifest.tpl');
+        $filepath = $path.'/imsmanifest.xml';
+        if (empty($expout)) {
+            error("Unkown error - empty imsmanifest.xml");
+        }
+        if (!$fh=fopen($filepath,"w")) {
+            error("Cannot open for writing: $filepath");
+        }
+        if (!fwrite($fh, $expout)) {
+            error("Cannot write exported questions to $filepath");
+        }
+        fclose($fh);
+
+        // iterate through questions
+        foreach($questions as $question) {
+            
+            // results are first written into string (and then to a file)
+            $count++;
+            echo "<hr /><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>";
+            $expout = $this->writequestion( $question , null, true, $path) . "\n";
+            $expout = $this->presave_process( $expout );
+            
+            $filepath = $path.'/'.$this->get_assesment_item_id($question) . ".xml";
+            if (!$fh=fopen($filepath,"w")) {
+                error("Cannot open for writing: $filepath");
+            }
+            if (!fwrite($fh, $expout)) {
+                error("Cannot write exported questions to $filepath");
+            }
+            fclose($fh);
+            
+        }
+
+        // zip files into single export file
+        zip_files( array($path), "$path.zip" );
+
+        // remove the temporary directory
+        remove_dir( $path );
+        return true;
+    }
+    
+/**
+ * exports a quiz (as opposed to exporting a category of questions)
+ *
+ * The parent class method was overridden because the IMS export consists of multiple files
+ *
+ * @param object $quiz
+ * @param array $questions - an array of question objects
+ * @param object $result - if set, contains result of calling quiz_grade_responses()
+ * @param string $redirect - a URL to redirect to in case of failure
+ * @param string $submiturl - the URL for the qti player to send the results to (e.g. attempt.php)
+ * @todo use $result in the ouput
+ */    
+     function export_quiz($course, $quiz, $questions, $result, $redirect, $submiturl = null) {
+        $this->xml_entitize($course);
+        $this->xml_entitize($quiz);
+        $this->xml_entitize($questions);
+        $this->xml_entitize($result);
+        $this->xml_entitize($submiturl);
+        if (! $this->exportpreprocess(0, $course)) {   // Do anything before that we need to
+            error("Error occurred during pre-processing!", $redirect);
+        }
+        if (! $this->exportprocess_quiz($quiz, $questions, $result, $submiturl, $course)) {         // Process the export data
+            error("Error occurred during processing!", $redirect);
+        }
+        if (! $this->exportpostprocess()) {                    // In case anything needs to be done after
+            error("Error occurred during post-processing!", $redirect);
+        }
+
+    }
+    
+
+/**
+ * This function is called to export a quiz (as opposed to exporting a category of questions)
+ *
+ * @uses $USER
+ * @param object $quiz
+ * @param array $questions - an array of question objects
+ * @param object $result - if set, contains result of calling quiz_grade_responses()
+ * @todo use $result in the ouput
+ */    
+    function exportprocess_quiz($quiz, $questions, $result, $submiturl, $course) {
+        global $USER;
+        global $CFG;
+
+        $gradingmethod = array (1 => 'GRADEHIGHEST',
+                                2 => 'GRADEAVERAGE',
+                                3 => 'ATTEMPTFIRST' ,
+                                4 => 'ATTEMPTLAST');
+
+        $questions = $this->quiz_export_prepare_questions($questions, $quiz->id, $course->id, $quiz->shuffleanswers);
+
+        $smarty =& $this->init_smarty();
+        $smarty->assign('questions', $questions);
+        
+        // quiz level smarty variables
+        $manifestid = str_replace(array(':', '/'), array('-','_'), "quiz{$quiz->id}-{$CFG->wwwroot}");
+        $smarty->assign('manifestidentifier', $manifestid);
+        $smarty->assign('submiturl', $submiturl);
+        $smarty->assign('userid', $USER->id);        
+        $smarty->assign('username', htmlspecialchars($USER->username, ENT_COMPAT, 'UTF-8'));
+        $smarty->assign('quiz_level_export', 1);
+        $smarty->assign('quiztitle', format_string($quiz->name,true)); //assigned specifically so as not to cause problems with category-level export
+        $smarty->assign('quiztimeopen', date('Y-m-d\TH:i:s', $quiz->timeopen)); // ditto
+        $smarty->assign('quiztimeclose', date('Y-m-d\TH:i:s', $quiz->timeclose)); // ditto
+        $smarty->assign('grademethod', $gradingmethod[$quiz->grademethod]);
+        $smarty->assign('quiz', $quiz);
+        $smarty->assign('course', $course);
+        $smarty->assign('lang', $this->lang);
+        $expout = $smarty->fetch('imsmanifest.tpl');
+        echo $expout;
+        return true;
+    }
+
+    
+    
+
+/**
+ * Prepares questions for quiz export
+ *
+ * The questions are changed as follows:
+ *   - the question answers atached to the questions
+ *   - image set to an http reference instead of a file path
+ *   - qti specific info added
+ *   - exporttext added, which contains an xml-formatted qti assesmentItem
+ *
+ * @param array $questions - an array of question objects
+ * @param int $quizid
+ * @return an array of question arrays
+ */    
+    function quiz_export_prepare_questions($questions, $quizid, $courseid, $shuffleanswers = null) {
+        global $CFG;
+        // add the answers to the questions and format the image property
+        foreach ($questions as $key=>$question) {
+            $questions[$key] = get_question_data($question);
+            $questions[$key]->courseid = $courseid;
+            $questions[$key]->quizid = $quizid;
+    
+            if ($question->image) {
+                
+                if (empty($question->mediamimetype)) {
+                  $questions[$key]->mediamimetype = mimeinfo('type',$question->image);  
+                }
+                
+                $localfile = (substr(strtolower($question->image), 0, 7) == 'http://') ? false : true;
+
+                if ($localfile) {
+                    // create the http url that the player will need to access the file
+                    if ($CFG->slasharguments) {        // Use this method if possible for better caching
+                        $questions[$key]->mediaurl = "$CFG->wwwroot/file.php/$question->image";
+                    } else {
+                        $questions[$key]->mediaurl = "$CFG->wwwroot/file.php?file=$question->image";
+                    } 
+                } else {
+                    $questions[$key]->mediaurl = $question->image;
+                }
+            }
+        }
+
+        $this->add_qti_info($questions);
+        $questions = $this->questions_with_export_info($questions, $shuffleanswers);
+        $questions = $this->objects_to_array($questions);
+        return $questions;
+    }
+
+/**
+ * calls htmlspecialchars for each string field, to convert, for example, & to &amp;
+ * 
+ * collections are processed recursively
+ *
+ * @param array $collection - an array or object or string
+ */    
+function xml_entitize(&$collection) {
+    if (is_array($collection)) {
+        foreach ($collection as $key=>$var) {
+            if (is_string($var)) {
+                $collection[$key]= htmlspecialchars($var, ENT_COMPAT, 'UTF-8');
+            } else if (is_array($var) || is_object($var)) {
+                $this->xml_entitize($collection[$key]);
+            } 
+        }
+    } else if (is_object($collection)) {
+        $vars = get_object_vars($collection);
+        foreach ($vars as $key=>$var) {
+            if (is_string($var)) {
+                $collection->$key = htmlspecialchars($var, ENT_COMPAT, 'UTF-8');
+            } else if (is_array($var) || is_object($var)) {
+                $this->xml_entitize($collection->$key);
+            } 
+        }
+    } else if (is_string($collection)) {
+        $collection = htmlspecialchars($collection, ENT_COMPAT, 'UTF-8');
+    }
+}
+/**
+ * adds exporttext property to the questions
+ *
+ * Adds the qti export text to the questions
+ *
+ * @param array $questions - an array of question objects
+ * @return an array of question objects
+ */    
+    function questions_with_export_info($questions, $shuffleanswers = null) {
+        $exportquestions = array();
+        foreach($questions as $key=>$question) {
+            $expout = $this->writequestion( $question , $shuffleanswers) . "\n";
+            $expout = $this->presave_process( $expout );
+            $key = $this->get_assesment_item_id($question);
+            $exportquestions[$key] = $question;
+            $exportquestions[$key]->exporttext = $expout;
+        }
+        return $exportquestions;
+    }
+
+/**
+ * Creates the export text for a question
+ *
+ * @todo handle in-line media (specified in the question/subquestion/answer text) for course-level exports
+ * @param object $question
+ * @param boolean $shuffleanswers whether or not to shuffle the answers
+ * @param boolean $courselevel whether or not this is a course-level export
+ * @param string $path provide the path to copy question media files to, if $courselevel == true
+ * @return string containing export text
+ */    
+    function writequestion($question, $shuffleanswers = null, $courselevel = false, $path = '') {
+        // turns question into string
+        // question reflects database fields for general question and specific to type
+        global $CFG;
+        $expout = '';
+        //need to unencode the html entities in the questiontext field.  
+        // the whole question object was earlier run throught htmlspecialchars in xml_entitize().
+        $question->questiontext = html_entity_decode($question->questiontext, ENT_COMPAT);
+        
+        $hasimage = empty($question->image) ? 0 : 1;
+        $hassize = empty($question->mediax) ? 0 : 1;
+        
+        $allowedtags = '<a><br><b><h1><h2><h3><h4><i><img><li><ol><strong><table><tr><td><th><u><ul><object>';  // all other tags will be stripped from question text
+        $smarty =& $this->init_smarty();
+        $assesmentitemid = $this->get_assesment_item_id($question);
+        $question_type = $this->get_qtype( $question->qtype );
+        $questionid = "question{$question->id}$question_type";
+        $smarty->assign('question_has_image', $hasimage);
+        $smarty->assign('hassize', $hassize);
+        $smarty->assign('questionid', $questionid);
+        $smarty->assign('assessmentitemidentifier', $assesmentitemid);
+        $smarty->assign('assessmentitemtitle', $question->name);
+        $smarty->assign('courselevelexport', $courselevel);
+        
+        if ($question->qtype == MULTIANSWER) {
+            $question->questiontext = strip_tags($question->questiontext, $allowedtags . '<intro>');
+            $smarty->assign('questionText',  $this->get_cloze_intro($question->questiontext));
+        } else {
+            $smarty->assign('questionText',  strip_tags($question->questiontext, $allowedtags));
+        }
+        
+        $smarty->assign('question', $question);
+        // the following two are left for compatibility; the templates should be changed, though, to make object tags for the questions
+        //$smarty->assign('questionimage', $question->image);
+        //$smarty->assign('questionimagealt', "image: $question->image");
+        
+        // output depends on question type
+        switch($question->qtype) {
+        case TRUEFALSE:
+            $qanswers = $question->options->answers;
+            $answers[0] = (array)$qanswers['true'];
+            $answers[0]['answer'] = get_string("true", "quiz");
+            $answers[1] = (array)$qanswers['false'];
+            $answers[1]['answer'] = get_string("false", "quiz");
+            
+            if (!empty($shuffleanswers)) {
+                $answers = $this->shuffle_things($answers);
+            }
+            
+        if (isset($question->response)) {
+              $correctresponseid = $question->response[$questionid];
+              if ($answers[0]['id'] == $correctresponseid) {
+                  $correctresponse = $answers[0];
+              } else {
+                  $correctresponse = $answers[1];
+              }
+            }
+            else {
+              $correctresponse = '';
+            }
+            
+            $smarty->assign('correctresponse', $correctresponse);
+            $smarty->assign('answers', $answers);
+            $expout = $smarty->fetch('choice.tpl');
+            break;
+        case MULTICHOICE:
+            $answers = $this->objects_to_array($question->options->answers);
+            if (!empty($shuffleanswers)) {
+                $answers = $this->shuffle_things($answers);
+            }
+            $correctresponses = $this->get_correct_answers($answers);
+            $correctcount = count($correctresponses);
+
+        
+            $smarty->assign('responsedeclarationcardinality', $correctcount > 1 ? 'multiple' : 'single');
+            $smarty->assign('correctresponses', $correctresponses);
+            $smarty->assign('answers', $answers);
+            $smarty->assign('maxChoices', $question->options->single ? '1' : count($answers));
+            $expout = $smarty->fetch('choiceMultiple.tpl');
+            break;
+        case SHORTANSWER:
+            $answers = $this->objects_to_array($question->options->answers);
+            if (!empty($shuffleanswers)) {
+                $answers = $this->shuffle_things($answers);
+            }
+            
+            $correctresponses = $this->get_correct_answers($answers);
+            $correctcount = count($correctresponses);
+
+            $smarty->assign('responsedeclarationcardinality', $correctcount > 1 ? 'multiple' : 'single');
+            $smarty->assign('correctresponses', $correctresponses);
+            $smarty->assign('answers', $answers);
+            $expout = $smarty->fetch('textEntry.tpl');
+            break;
+        case NUMERICAL:
+            $qanswer = array_pop( $question->options->answers );
+            $smarty->assign('lowerbound', $qanswer->answer - $qanswer->tolerance);        
+            $smarty->assign('upperbound', $qanswer->answer + $qanswer->tolerance);        
+            $smarty->assign('answer', $qanswer->answer);        
+            $expout = $smarty->fetch('numerical.tpl');
+            break;
+        case MATCH:
+            $this->xml_entitize($question->options->subquestions);
+            $subquestions = $this->objects_to_array($question->options->subquestions);
+            if (!empty($shuffleanswers)) {
+                $subquestions = $this->shuffle_things($subquestions);
+            }
+            $setcount = count($subquestions);
+        
+            $smarty->assign('setcount', $setcount);
+            $smarty->assign('matchsets', $subquestions);
+            $expout = $smarty->fetch('match.tpl');
+            break;
+        case DESCRIPTION:
+            $expout = $smarty->fetch('extendedText.tpl');
+            break;
+        // loss of get_answers() from quiz_embedded_close_qtype class during
+        // Gustav's refactor breaks MULTIANSWER badly - one for another day!!
+        /*
+        case MULTIANSWER:
+            $answers = $this->get_cloze_answers_array($question);
+            $questions = $this->get_cloze_questions($question, $answers, $allowedtags);
+            
+            $smarty->assign('cloze_trailing_text_id', CLOZE_TRAILING_TEXT_ID);            
+            $smarty->assign('answers', $answers);
+            $smarty->assign('questions', $questions);
+            $expout = $smarty->fetch('composite.tpl');
+            break; */
+        default:
+            $smarty->assign('questionText', "This question type (Unknown: type $question_type)  has not yet been implemented");
+            $expout = $smarty->fetch('notimplemented.tpl');
+        }
+    
+        // run through xml tidy function
+        //$tidy_expout = $this->indent_xhtml( $expout, '    ' ) . "\n\n";
+        //return $tidy_expout;
+        return $expout;
+    }
+
+/**
+ * Gets an id to use for a qti assesment item
+ *
+ * @param object $question
+ * @return string containing a qti assesment item id
+ */    
+    function get_assesment_item_id($question) {
+        return "question{$question->id}";
+    }
+
+/**
+ * gets the answers whose grade fraction > 0
+ *
+ * @param array $answers
+ * @return array (0-indexed) containing the answers whose grade fraction > 0
+ */    
+    function get_correct_answers($answers)
+    {
+        $correctanswers = array();
+        foreach ($answers as $answer) {
+            if ($answer['fraction'] > 0) {
+                $correctanswers[] = $answer;
+            }
+        }
+        return $correctanswers;
+    }
+
+/**
+ * gets a new Smarty object, with the template and compile directories set
+ *
+ * @return object a smarty object
+ */    
+    function & init_smarty() {
+        global $CFG;
+
+        // create smarty compile dir in dataroot
+        $path = $CFG->dataroot."/smarty_c";
+        if (!is_dir($path)) {
+            if (!mkdir($path, $CFG->directorypermissions)) {
+              error("Cannot create path: $path");
+            }
+        }
+        $smarty = new Smarty;
+        $smarty->template_dir = "{$CFG->dirroot}/question/format/qti_two/templates";
+        $smarty->compile_dir  = "$path";
+        return $smarty;
+    }
+
+/**
+ * converts an array of objects to an array of arrays (not recursively)
+ *
+ * @param array $objectarray
+ * @return array - an array of answer arrays
+ */    
+    function objects_to_array($objectarray)
+    {
+        $arrayarray = array();
+        foreach ($objectarray as $object) {
+            $arrayarray[] = (array)$object;
+        }
+        return $arrayarray;
+    }
+    
+/**
+ * gets a question's cloze answer objects as arrays containing only arrays and basic data types
+ *
+ * @param object $question
+ * @return array - an array of answer arrays
+ */    
+    function get_cloze_answers_array($question) {
+        $answers = $this->get_answers($question);
+        $this->xml_entitize($answers);
+        foreach ($answers as $answerkey => $answer) {
+            $answers[$answerkey]->subanswers = $this->objects_to_array($answer->subanswers);
+        }
+        return $this->objects_to_array($answers);
+    }
+    
+/**
+ * gets an array with text and question arrays for the given cloze question
+ *
+ * To make smarty processing easier, the returned text and question sub-arrays have an equal number of elements.
+ * If it is necessary to add a dummy element to the question sub-array, the question will be given an id of CLOZE_TRAILING_TEXT_ID.
+ *
+ * @param object $question
+ * @param array $answers - an array of arrays containing the question's answers
+ * @param string $allowabletags - tags not to strip out of the question text (e.g. '<i><br>')
+ * @return array with text and question arrays for the given cloze question
+ */    
+     function get_cloze_questions($question, $answers, $allowabletags) {
+        $questiontext = strip_tags($question->questiontext, $allowabletags);
+        if (preg_match_all('/(.*){#([0-9]+)}/U', $questiontext, $matches)) {
+            // matches[1] contains the text inbetween the question blanks
+            // matches[2] contains the id of the question blanks (db: question_multianswer.positionkey)
+            
+            // find any trailing text after the last {#XX} and add it to the array
+            if (preg_match('/.*{#[0-9]+}(.*)$/', $questiontext, $tail)) {
+                $matches[1][] = $tail[1];
+                $tailadded = true;
+            }
+            $questions['text'] = $matches[1];
+            $questions['question'] = array();
+            foreach ($matches[2] as $key => $questionid) {
+                foreach ($answers as $answer) {
+                    if ($answer['positionkey'] == $questionid) {
+                        $questions['question'][$key] = $answer;
+                        break;
+                    }
+                }
+            }
+            if ($tailadded) {
+                // to have a matching number of question and text array entries:
+                $questions['question'][] = array('id'=>CLOZE_TRAILING_TEXT_ID, 'answertype'=>SHORTANSWER);
+            }
+    
+        } else {
+            $questions['text'][0] = $question->questiontext;
+            $questions['question'][0] = array('id'=>CLOZE_TRAILING_TEXT_ID, 'answertype'=>SHORTANSWER);
+        }
+        
+        return $questions;
+    }
+    
+/**
+ * strips out the <intro>...</intro> section, if any, and returns the text
+ *
+ * changes the text object passed to it.
+ *
+ * @param string $&text
+ * @return string the intro text, if there was an intro tag. '' otherwise.
+ */    
+    function get_cloze_intro(&$text) {
+        if (preg_match('/(.*)?\<intro>(.+)?\<\/intro>(.*)/s', $text, $matches)) {
+            $text = $matches[1] . $matches[3];
+            return $matches[2];
+        }
+        else {
+            return '';
+        }
+    }             
+    
+
+/**
+ * adds qti metadata properties to the questions
+ *
+ * The passed array of questions is altered by this function 
+ *
+ * @param &questions an array of question objects
+ */    
+    function add_qti_info(&$questions)
+    {
+        foreach ($questions as $key=>$question) {
+            $questions[$key]->qtiinteractiontype = $this->get_qti_interaction_type($question->qtype);
+            $questions[$key]->qtiscoreable = $this->get_qti_scoreable($question);
+            $questions[$key]->qtisolutionavailable = $this->get_qti_solution_available($question);
+        }
+        
+    }
+    
+/**
+ * returns whether or not a given question is scoreable
+ *
+ * @param object $question
+ * @return boolean
+ */    
+    function get_qti_scoreable($question) {
+        switch ($question->qtype) {
+            case DESCRIPTION:
+                return 'false';
+            default:
+                return 'true';
+        }
+    }
+    
+/**
+ * returns whether or not a solution is available for a given question 
+ *
+ * The results are based on whether or not Moodle stores answers for the given question type
+ *
+ * @param object $question
+ * @return boolean
+ */    
+    function get_qti_solution_available($question) {
+        switch($question->qtype) {
+            case TRUEFALSE:
+                return 'true';
+            case MULTICHOICE:
+                return 'true';
+            case SHORTANSWER:
+                return 'true';
+            case NUMERICAL:
+                return 'true';
+            case MATCH:
+                return 'true';
+            case DESCRIPTION:
+                return 'false';
+            case MULTIANSWER:
+                return 'true';
+            default:
+                return 'true';
+        }
+    
+    }
+    
+/**
+ * maps a moodle question type to a qti 2.0 question type
+ *
+ * @param int type_id - the moodle question type
+ * @return string qti 2.0 question type
+ */    
+    function get_qti_interaction_type($type_id) {
+        switch( $type_id ) {
+        case TRUEFALSE:
+            $name = 'choiceInteraction';
+            break;
+        case MULTICHOICE:
+            $name = 'choiceInteraction';
+            break;
+        case SHORTANSWER:
+            $name = 'textInteraction';
+            break;
+        case NUMERICAL:
+            $name = 'textInteraction';
+            break;
+        case MATCH:
+            $name = 'matchInteraction';
+            break;
+        case DESCRIPTION:
+            $name = 'extendedTextInteraction';
+            break;
+        case MULTIANSWER:
+            $name = 'textInteraction';
+            break;
+        default:
+            $name = 'textInteraction';
+        }
+        return $name;
+    }
+
+/**
+ * returns the given array, shuffled
+ *
+ *
+ * @param array $things
+ * @return array
+ */    
+    function shuffle_things($things) {
+        $things = swapshuffle_assoc($things);    
+        $oldthings = $things;
+        $things = array();
+        foreach ($oldthings as $key=>$value) {
+            $things[] = $value;      // This loses the index key, but doesn't matter
+        }
+        return $things;
+    }
+    
+/**
+ * returns a flattened image name - with all /, \ and : replaced with other characters
+ *
+ * used to convert a file or url to a qti-permissable identifier
+ *
+ * @param string name
+ * @return string 
+ */    
+    function flatten_image_name($name) {
+        return str_replace(array('/', '\\', ':'), array ('_','-','.'), $name);
+    }
+
+    function file_full_path($file, $courseid) {
+        global $CFG;
+        if (substr(strtolower($file), 0, 7) == 'http://') {
+            $url = $file;
+        } else if ($CFG->slasharguments) {        // Use this method if possible for better caching
+            $url = "{$CFG->wwwroot}/file.php/$courseid/{$file}";
+        } else {
+            $url = "{$CFG->wwwroot}/file.php?file=/$courseid/{$file}";
+        }
+        return $url;
+    }
+
+}
+
+?>
diff --git a/question/format/qti_two/qt_common.php b/question/format/qti_two/qt_common.php
new file mode 100644 (file)
index 0000000..10229a2
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+
+//***********************************
+// qt_common.php
+//***********************************
+// This contains code common to mediagonal-modified questions
+//
+
+/**
+ * gets a list of all the media files for the given course
+ *
+ * @param int courseid
+ * @return array containing filenames
+ * @calledfrom type/<typename>/editquestion.php 
+ * @package questionbank
+ * @subpackage importexport
+ */
+function get_course_media_files($courseid) 
+{
+// this code lifted from mod/quiz/question.php and modified
+    global $CFG;
+    $images = null;
+    
+    make_upload_directory("$course->id");    // Just in case
+    $coursefiles = get_directory_list("$CFG->dataroot/$courseid", $CFG->moddata);
+    foreach ($coursefiles as $filename) {
+        if (is_media_by_extension($filename)) {
+            $images["$filename"] = $filename;
+        }
+    }
+    return $images;
+}    
+
+/**
+ * determines whether or not a file is an image, based on the file extension
+ *
+ * @param string $file the filename
+ * @return boolean
+ */
+function is_image_by_extentsion($file) {
+    $extensionsregex = '/\.(gif|jpg|jpeg|jpe|png|tif|tiff|bmp|xbm|rgb|svf)$/';
+    if (preg_match($extensionsregex, $file)) {
+        return true;
+    }
+    return false;
+}
+
+
+/**
+ * determines whether or not a file is a media file, based on the file extension
+ *
+ * @param string $file the filename
+ * @return boolean
+ */
+function is_media_by_extension($file) {
+    $extensionsregex = '/\.(gif|jpg|jpeg|jpe|png|tif|tiff|bmp|xbm|rgb|svf|swf|mov|mpg|mpeg|wmf|avi|mpe|flv|mp3|ra|ram)$/';
+    if (preg_match($extensionsregex, $file)) {
+        return true;
+    }
+    return false;
+}
+
+/**
+ * determines whether or not a file is a multimedia file, based on the file extension
+ *
+ * @param string $file the filename
+ * @return boolean
+ */
+function is_multimedia_by_extension($file) {
+    $extensionsregex = '/\.(swf|mov|mpg|mpeg|wmf|avi|mpe|flv)$/';
+    if (preg_match($extensionsregex, $file)) {
+        return true;
+    }
+    return false;
+}
+
+/**
+ * determines whether or not a file is a multimedia file of a type php can get the dimension for, based on the file extension
+ *
+ * @param string $file the filename
+ * @return boolean
+ */
+function is_sizable_multimedia($file) {
+    $extensionsregex = '/\.(swf)$/';
+    if (preg_match($extensionsregex, $file)) {
+        return true;
+    }
+    return false;
+}
+
+/**
+ * creates a media tag to use for choice media
+ *
+ * @param string $file the filename
+ * @param string $courseid the course id
+ * @param string $alt to specify the alt tag
+ * @return string either an image tag, or html for an embedded object 
+ */
+function get_media_tag($file, $courseid = 0, $alt = 'media file', $width = 0, $height = 0) {
+    global $CFG;
+
+    // if it's a moodle library file, it will be served through file.php
+    if (substr(strtolower($file), 0, 7) == 'http://') {
+        $media = $file;
+    } else if ($CFG->slasharguments) {        // Use this method if possible for better caching
+        $media = "{$CFG->wwwroot}/file.php/$courseid/$file";
+    } else {
+        $media = "{$CFG->wwwroot}/file.php?file=/$courseid/$file";
+    }
+
+    $ismultimedia = false;
+    if (!$isimage = is_image_by_extension($file)) {
+           $ismultimedia = is_multimedia_by_extension($file);
+    }
+    
+    // if there is no known width and height, try to get one
+    if ($width == 0) {
+         if ($isimage || is_sizable_multimedia($file)) {
+             
+         }
+        
+    } 
+    // create either an image link or a generic link.
+    // if the moodle multimedia filter is turned on, it'll catch multimedia content in the generic link
+    if (is_image_by_extension($file)) {
+        return "<img src=\"$media\" alt=\"$alt\" width=\"$width\" height=\"$height\" />";
+    }
+    else {
+        require_once("$CFG->dirroot/mod/quiz/format/qti/custommediafilter.php");
+        return custom_mediaplugin_filter('<a href="' . $media . '"></a>', $courseid, $width, $height);
+    }
+}
+
+/**
+ * determines the x and y size of the given file
+ *
+ * @param string $file the filename 
+ * @return array looks like array('x'=>171, 'y'=>323), or array('x'=>0, 'y'=>0) if size can't be determined
+ */
+function get_file_dimensions($file) {
+    $imginfo = @getimagesize($file);
+    if ($imginfo !== FALSE) {
+        return array('x'=>$imginfo[0], 'y'=>$imginfo[1]);
+    } else {
+        return array('x'=> 0, 'y'=> 0);
+    }
+}
+
+?>
diff --git a/question/format/qti_two/templates/choice.tpl b/question/format/qti_two/templates/choice.tpl
new file mode 100755 (executable)
index 0000000..3a06d33
--- /dev/null
@@ -0,0 +1,70 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="single" baseType="identifier">
+               <correctResponse>
+                       <value>{$correctresponse.id}</value>
+               </correctResponse>
+               <mapping defaultValue="0">
+                       <mapEntry mapKey="{$correctresponse.id}" mappedValue="{$correctresponse.fraction}"/>
+               </mapping>
+
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float">
+               <defaultValue>
+                       <value>0</value>
+               </defaultValue>
+       </outcomeDeclaration>
+       <itemBody>
+               <p>{$questionText}</p>
+               <div class="intreactive.choiceSimple">
+                       <choiceInteraction responseIdentifier="{$questionid}" shuffle="false" maxChoices="1">
+               {section name=answer loop=$answers}
+                               <simpleChoice identifier="{$answers[answer].id}">{$answers[answer].answer}                
+                               {if $answers[answer].feedback != ''}
+                               {if $answers[answer].answer != $correctresponse.answer}
+                                   <feedbackInline identifier="{$answers[answer].id}" outcomeIdentifier="FEEDBACK" showHide="hide">{$answers[answer].feedback}</feedbackInline>
+                    {/if}
+                {/if}
+                               </simpleChoice>
+               {/section}
+                       </choiceInteraction>
+       {if $question_has_image == 1}
+            <div class="media">
+           {if $hassize == 1}
+                        <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                        <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+            </div>
+       {/if}
+               </div>
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+                       <responseIf>
+
+                               <match>
+                                       <variable identifier="{$questionid}"/>
+                                       <correct identifier="{$questionid}"/>
+                               </match>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="float">1</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="float">0</baseValue>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+        <setOutcomeValue identifier="FEEDBACK">
+            <variable identifier="{$questionid}"/>
+        </setOutcomeValue>             
+       </responseProcessing>
+       {section name=answer loop=$answers}
+        {if $answers[answer].feedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$answers[answer].id}" showHide="hide">{$answers[answer].feedback}</modalFeedback>
+       {/if}
+       {/section}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/choiceMultiple.tpl b/question/format/qti_two/templates/choiceMultiple.tpl
new file mode 100755 (executable)
index 0000000..1746330
--- /dev/null
@@ -0,0 +1,64 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="{$responsedeclarationcardinality}" baseType="identifier">
+               <correctResponse>
+               {section name=answer loop=$correctresponses}
+                       <value>{$correctresponses[answer].id}</value>
+               {/section}
+               </correctResponse>
+               <mapping lowerBound="0" upperBound="1" defaultValue="-1">
+               {section name=answer loop=$answers}
+                   {if $answers[answer].fraction != 0}
+                       <mapEntry mapKey="{$answers[answer].id}" mappedValue="{$answers[answer].fraction}" />
+                       {/if}
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float" />
+       <itemBody>
+          <div class="assesmentItemBody">
+               <p>{$questionText}</p>
+       </div>
+               <div class="interactive.choiceMultiple">
+                       <choiceInteraction responseIdentifier="{$questionid}" shuffle="false" maxChoices="{$maxChoices}">
+               {section name=answer loop=$answers}
+                               <simpleChoice identifier="{$answers[answer].id}" fixed="false">{$answers[answer].answer}</simpleChoice>
+               {/section}
+                       </choiceInteraction>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+                       <responseIf>
+                               <isNull>
+                                       <variable identifier="{$questionid}"/>
+                               </isNull>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="float">0</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <mapResponse identifier="{$questionid}"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+        <setOutcomeValue identifier="FEEDBACK">
+            <variable identifier="{$questionid}"/>
+        </setOutcomeValue>             
+       </responseProcessing>
+       {section name=answer loop=$answers}
+        {if $answers[answer].feedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$answers[answer].id}" showHide="show">{$answers[answer].feedback}</modalFeedback>
+       {/if}
+       {/section}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/composite.tpl b/question/format/qti_two/templates/composite.tpl
new file mode 100755 (executable)
index 0000000..802cd08
--- /dev/null
@@ -0,0 +1,101 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       {section name=aid loop=$answers}
+       {if $answers[aid].answertype == 3}
+       <responseDeclaration identifier="{$questionid}{$answers[aid].id}" cardinality="single" baseType="identifier">
+               <correctResponse>
+           {section name=subanswer loop=$answers[aid].subanswers}
+           {if $answers[aid].subanswers[subanswer].fraction > 0}
+                       <value>{$answers[aid].subanswers[subanswer].id}</value>
+               {/if}
+               {/section}
+               </correctResponse>
+               <mapping defaultValue="0">
+           {section name=subanswer loop=$answers[aid].subanswers}
+           {if $answers[aid].subanswers[subanswer].fraction != 0}
+                       <mapEntry mapKey="{$answers[aid].subanswers[subanswer].id}" mappedValue="{$answers[aid].subanswers[subanswer].fraction}"/>
+               {/if}
+               {/section}
+               </mapping>
+       </responseDeclaration>
+    {elseif $answers[aid].answertype == 1}
+       <responseDeclaration identifier="{$questionid}{$answers[aid].id}" cardinality="single" baseType="string">
+               <correctResponse>
+               {section name=subanswer loop=$answers[aid].subanswers}
+               {if $answers[aid].subanswers[subanswer].fraction > 0}
+                       <value>{$answers[aid].subanswers[subanswer].answer}</value>
+               {/if}
+               {/section}
+               </correctResponse>
+               <mapping lowerBound="0" upperBound="1" defaultValue="0">
+               {section name=subanswer loop=$answers[aid].subanswers}
+                   {if $answers[aid].subanswers[subanswer].fraction != 0}
+                       <mapEntry mapKey="{$answers[aid].subanswers[subanswer].answer}" mappedValue="{$answers[aid].subanswers[subanswer].fraction}" />
+                       {/if}
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       {/if}
+       {/section}
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
+       <itemBody>
+       {if $questionText != ''}
+           <div class="assesmentItemBody">
+               <p>{$questionText}</p>
+               </div>
+       {/if}
+       <div class="interactive.cloze"><p>
+       {section name=qid loop=$questions.question}
+           {$questions.text[qid]}
+           {if $questions.question[qid].id != $cloze_trailing_text_id}
+               {if $questions.question[qid].answertype == 3}
+                       <inlineChoiceInteraction responseIdentifier="{$questionid}{$questions.question[qid].id}" shuffle="false">
+                    {section name=aid loop=$questions.question[qid].subanswers}
+                                          <inlineChoice identifier="{$questions.question[qid].subanswers[aid].id}">{$questions.question[qid].subanswers[aid].answer}</inlineChoice>
+                                       {/section}
+                               </inlineChoiceInteraction>
+               {elseif $questions.question[qid].answertype == 1}
+                <textEntryInteraction responseIdentifier="{$questionid}{$questions.question[qid].id}" expectedLength="15"/>
+               {/if}
+       {/if}
+       {/section}</p></div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+{section name=answer loop=$answers}{if $answers[answer].answertype == 1 || $answers[answer].answertype == 3}
+               <responseCondition>
+                       <responseIf>
+                               <isNull>
+                                       <variable identifier="{$questionid}{$answers[answer].id}"/>
+                               </isNull>
+                               <setOutcomeValue identifier="SCORE{$questionid}{$answers[answer].id}">
+                                       <baseValue baseType="float">0</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE{$questionid}{$answers[answer].id}">
+                                       <mapResponse identifier="{$questionid}{$answers[answer].id}"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+        <setOutcomeValue identifier="FEEDBACK">
+            <variable identifier="{$questionid}{$answers[answer].id}"/>
+        </setOutcomeValue>             
+{/if}{/section}
+       </responseProcessing>
+{section name=answer loop=$answers}{if $answers[answer].answertype == 1 || $answers[answer].answertype == 3}
+          {section name=subanswer loop=$answers[answer].subanswers}
+       {if $answers[answer].subanswers[subanswer].feedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$answers[answer].subanswers[subanswer].id}" showHide="show">{$answers[answer].subanswers[subanswer].feedback}</modalFeedback>
+{/if}{/section}
+    {/if}   
+       {/section}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/extendedText.tpl b/question/format/qti_two/templates/extendedText.tpl
new file mode 100755 (executable)
index 0000000..52d2bd9
--- /dev/null
@@ -0,0 +1,17 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="single" baseType="string"/>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="integer"/>
+       <itemBody>
+               <p>{$questionText}</p>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+</assessmentItem>
diff --git a/question/format/qti_two/templates/extendedText_simpleEssay.tpl b/question/format/qti_two/templates/extendedText_simpleEssay.tpl
new file mode 100755 (executable)
index 0000000..76d7d23
--- /dev/null
@@ -0,0 +1,27 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="single" baseType="string"/>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="integer"/>
+       <itemBody>
+           <div class="assesmentItemBody">
+               <p>{$questionText}</p>
+               </div>
+               <div class="interactive.extendedText">
+                       <extendedTextInteraction responseIdentifier="{$questionid}" expectedLength="600">
+                       </extendedTextInteraction>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+{if $question->feedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$questionid}" showHide="hide">{$question->feedback}</modalFeedback>
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$questionid}" showHide="show">{$question->feedback}</modalFeedback>
+{/if}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/graphicGapMatch.tpl b/question/format/qti_two/templates/graphicGapMatch.tpl
new file mode 100644 (file)
index 0000000..e2b5d06
--- /dev/null
@@ -0,0 +1,33 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="multiple" baseType="directedPair">
+               <correctResponse>
+               {section name=item loop=$gapitems}
+                               <value>{$gapitems[item].id} {$gapitems[item].id}</value>
+               {/section}
+               </correctResponse>
+               <mapping defaultValue="1">
+               {section name=item loop=$gapitems}
+                               <mapEntry mapKey="{$gapitems[item].id} {$gapitems[item].id}" mappedValue="1" />
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
+       <itemBody>
+               <div class="assesmentItemBody"><p>{$questionText}</p></div>
+               <div class="interactive.graphicGapMatch">
+                       <graphicGapMatchInteraction responseIdentifier="{$questionid}">
+                               <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}"/>
+                       {section name=item loop=$gapitems}
+                               <gapImg identifier="{$gapitems[item].id}" matchMax="1">
+                                       <object type="{$gapitems[item].mediamimetype}" data="{$gapitems[item].media}" width="{$gapitems[item].snaptowidth}" height="{$gapitems[item].snaptoheight}" label="{$gapitems[item].questiontext}"/>
+                               </gapImg>
+                       {/section}
+                       {section name=item loop=$gapitems}
+                       <associableHotspot identifier="{$gapitems[item].id}" matchMax="{$hotspotmaxmatch}" shape="rect" coords="{$gapitems[item].targetx},{$gapitems[item].targety},{$gapitems[item].targetrx},{$gapitems[item].targetby}"/>
+                       {/section}
+                       </graphicGapMatchInteraction>
+               </div>
+       </itemBody>
+       <responseProcessing template="http://www.imsglobal.org/xml/imsqti_item_v2p0/rpMapResponse" templateLocation="../RPTemplates/rpMapResponse.xml"/>
+</assessmentItem>
\ No newline at end of file
diff --git a/question/format/qti_two/templates/imsmanifest.tpl b/question/format/qti_two/templates/imsmanifest.tpl
new file mode 100755 (executable)
index 0000000..570d35a
--- /dev/null
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<manifest xmlns="http://www.imsglobal.org/xsd/imscp_v1p1" xmlns:imsmd="http://www.imsglobal.org/xsd/imsmd_v1p2" xmlns:imsqti="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" identifier="{$manifestidentifier}" xsi:schemaLocation="http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd   http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p2.xsd  http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd">
+       <metadata>
+               <schema>ADL SCORM</schema>
+               <schemaversion>1.2</schemaversion>
+               <lom xmlns="http://www.imsglobal.org/xsd/imsmd_v1p2" 
+                       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+                       xsi:schemaLocation="http://www.imsglobal.org/xsd/imsmd_v1p2 imsmd_v1p2p2.xsd">
+               <general>
+                               <title><langstring xml:lang="{$lang}">{$quiztitle}</langstring></title>
+                               <description><langstring xml:lang="{$lang}">{$quizinfo}</langstring></description>
+                               <keyword><langstring xml:lang="{$lang}">{$quizkeywords}</langstring></keyword>
+                       </general>
+               </lom>
+               {if $quiz_level_export == 1}
+               <imsqti:var id="submiturl">{$submiturl}</imsqti:var>
+               <imsqti:var id="userid">{$userid}</imsqti:var>
+               <imsqti:var id="username">{$username}</imsqti:var>
+               <imsqti:var id="id">{$quiz->id}</imsqti:var>
+               <imsqti:var id="course">{$quiz->course}</imsqti:var>
+               <imsqti:var id="timeopen">{$quiztimeopen}</imsqti:var>
+               <imsqti:var id="timeclose">{$quiztimeclose}</imsqti:var>
+               <imsqti:var id="timelimit">{$quiz->timelimit}</imsqti:var>
+               <imsqti:var id="shufflequestions">{$quiz->shufflequestions}</imsqti:var>
+               <imsqti:var id="shuffleanswers">{$quiz->shuffleanswers}</imsqti:var>
+               <imsqti:var id="attempts">{$quiz->attempts}</imsqti:var>
+               <imsqti:var id="attemptbuildsonlast">{$quiz->attemptonlast}</imsqti:var>
+               <imsqti:var id="grademethod">{$grademethod}</imsqti:var>
+               <imsqti:var id="feedback">{$quiz->feedback}</imsqti:var>
+               <imsqti:var id="feedbackcorrectanswers">{$quiz->correctanswers}</imsqti:var>
+               <imsqti:var id="maxgrade">{$quiz->grade}</imsqti:var>
+               <imsqti:var id="rawpointspossible">{$quiz->sumgrades}</imsqti:var>
+               <imsqti:var id="password">{$quiz->password}</imsqti:var>
+               <imsqti:var id="subnet">{$quiz->subnet}</imsqti:var>
+               <imsqti:var id="coursefullname">{$course->fullname}</imsqti:var>
+               <imsqti:var id="courseshortname">{$course->shortname}</imsqti:var>
+               {/if}
+       </metadata>
+       <organizations/>
+       <resources>
+       {section name=question loop=$questions}
+               <resource identifier="category{$questions[question].category}-question{$questions[question].id}" type="imsqti_item_xmlv2p0" {if $externalfiles == 1}href="./category{$questions[question].category}-question{$questions[question].id}.xml"{/if}>
+                       <metadata>
+                               <schema>IMS QTI Item</schema>
+                               <schemaversion>2.0</schemaversion>
+                               <imsmd:lom>
+                                       <imsmd:general>
+                                               <imsmd:identifier>category{$questions[question].category}-question{$questions[question].id}</imsmd:identifier>
+                                               <imsmd:title>
+                                                       <imsmd:langstring xml:lang="{$lang}">{$questions[question].name}</imsmd:langstring>
+                                               </imsmd:title>
+                                               <imsmd:description>
+                                                       <imsmd:langstring xml:lang="en">Question {$questions[question].id} from category {$questions[question].category}</imsmd:langstring>
+                                               </imsmd:description>
+                                       </imsmd:general>
+                                       <imsmd:lifecycle>
+                                               <imsmd:version>
+                                                       <imsmd:langstring xml:lang="en">1.0</imsmd:langstring>
+                                               </imsmd:version>
+                                               <imsmd:status>
+                                                       <imsmd:source>
+                                                               <imsmd:langstring xml:lang="en">LOMv1.0</imsmd:langstring>
+                                                       </imsmd:source>
+                                                       <imsmd:value>
+                                                               <imsmd:langstring xml:lang="en">Draft</imsmd:langstring>
+                                                       </imsmd:value>
+                                               </imsmd:status>
+                                       </imsmd:lifecycle>
+                               </imsmd:lom>
+                               <imsqti:qtiMetadata>
+                                       <imsqti:timeDependent>false</imsqti:timeDependent>
+                                       <imsqti:interactionType>{$questions[question].qtiinteractiontype}</imsqti:interactionType>
+                                       <imsqti:canComputerScore>{$questions[question].qtiscoreable}</imsqti:canComputerScore>
+                                       <imsqti:feedbackType>nonadaptive</imsqti:feedbackType>
+                                       <imsqti:solutionAvailable>{$questions[question].qtisolutionavailable}</imsqti:solutionAvailable>
+                               </imsqti:qtiMetadata>
+                       </metadata>
+                       {if $questions[question].image != ''}
+                       <file href="{$questions[question].mediaurl}" />
+                       {/if}
+                       {$questions[question].exporttext}
+               </resource>
+               {/section}
+       </resources>
+</manifest>
\ No newline at end of file
diff --git a/question/format/qti_two/templates/match.tpl b/question/format/qti_two/templates/match.tpl
new file mode 100644 (file)
index 0000000..e124632
--- /dev/null
@@ -0,0 +1,62 @@
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="multiple" baseType="directedPair">
+               <correctResponse>
+               {section name=set loop=$matchsets}
+                               <value>q{$matchsets[set].id} a{$matchsets[set].id}</value>
+               {/section}
+               </correctResponse>
+
+               <mapping defaultValue="0">
+               {section name=set loop=$matchsets}
+                  <mapEntry mapKey="q{$matchsets[set].id} a{$matchsets[set].id}" mappedValue="1"/>
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
+
+       <itemBody>
+               <p>{$questionText}</p>
+               <div class="interactive.match">
+                       <matchInteraction responseIdentifier="{$questionid}" shuffle="false" maxAssociations="{$setcount}">
+                               <simpleMatchSet>
+                       {section name=set loop=$matchsets}
+                               <simpleAssociableChoice identifier="q{$matchsets[set].id}" matchMax="1">{$matchsets[set].questiontext}</simpleAssociableChoice>
+                       {/section}
+                               </simpleMatchSet>
+                               <simpleMatchSet>
+                       {section name=set loop=$matchsets}
+                               <simpleAssociableChoice identifier="a{$matchsets[set].id}" matchMax="{$setcount}">{$matchsets[set].answertext}</simpleAssociableChoice>
+                       {/section}
+                               </simpleMatchSet>
+                       </matchInteraction>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+
+                       <responseIf>
+                               <isNull>
+                                       <variable identifier="{$questionid}"/>
+                               </isNull>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="integer">0</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <mapResponse identifier="{$questionid}"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+       </responseProcessing>
+</assessmentItem>
diff --git a/question/format/qti_two/templates/mmchoiceMultiple.tpl b/question/format/qti_two/templates/mmchoiceMultiple.tpl
new file mode 100755 (executable)
index 0000000..558e077
--- /dev/null
@@ -0,0 +1,88 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="{$responsedeclarationcardinality}" baseType="identifier">
+               <correctResponse>
+               {section name=answer loop=$correctresponses}
+                       <value>{$correctresponses[answer].id}</value>
+               {/section}
+               </correctResponse>
+               <mapping lowerBound="0" upperBound="1" defaultValue="{$defaultvalue}">
+               {section name=answer loop=$answers}
+                   {if $answers[answer].fraction != 0}
+                       <mapEntry mapKey="{$answers[answer].id}" mappedValue="{$answers[answer].fraction}" />
+                       {/if}
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float" />
+       <itemBody>
+          <div class="assesmentItemBody">
+               <p>{$questionText}</p>
+       </div>
+               <div class="interactive.choiceMultiple">
+                       <choiceInteraction responseIdentifier="{$questionid}" shuffle="false" maxChoices="{$maxChoices}">
+               {section name=answer loop=$answers}
+                               <simpleChoice identifier="{$answers[answer].id}" fixed="false"><p>{$answers[answer].choice}
+                       {if $answers[answer].media != ''}
+                               <object type="{$answers[answer].mediamimetype}" data="{$answers[answer].media}" width="{$answers[answer].mediax}" height="{$answers[answer].mediay}" />
+                       {/if}</p>
+                       {if $answers[answer].feedback != ''}
+                                   <feedbackInline identifier="{$answers[answer].id}" outcomeIdentifier="FEEDBACK" showHide="show">{$answers[answer].feedback}</feedbackInline>
+                {/if}
+                       {if $answers[answer].altfeedback != ''}
+                                   <feedbackInline identifier="{$answers[answer].id}" outcomeIdentifier="ALTFEEDBACK" showHide="hide">{$answers[answer].altfeedback}</feedbackInline>
+                {/if}
+                               </simpleChoice>
+               {/section}
+                       </choiceInteraction>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+                       <responseIf>
+                               <isNull>
+                                       <variable identifier="{$questionid}"/>
+                               </isNull>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="float">0</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <mapResponse identifier="{$questionid}"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+               <responseCondition>
+                       <responseIf>
+                               <gte>
+                                       <variable identifier="SCORE"/>
+                                       <baseValue baseType="float">{$question->feedbackfraction}</baseValue>
+                               </gte>
+                               <setOutcomeValue identifier="FEEDBACK">
+                                       <variable identifier="feedbackok"/>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="FEEDBACK">
+                                       <variable identifier="feedbackmissed"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+       </responseProcessing>
+{if $question->feedbackok != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="feedbackok" showHide="show">{$question->feedbackok}</modalFeedback>
+{/if}
+{if $question->feedbackmissed != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="feedbackmissed" showHide="hide">{$question->feedbackmissed}</modalFeedback>
+{/if}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/notimplemented.tpl b/question/format/qti_two/templates/notimplemented.tpl
new file mode 100755 (executable)
index 0000000..e431354
--- /dev/null
@@ -0,0 +1,20 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="single" baseType="string"/>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="integer"/>
+       <itemBody>
+               <p>{$questionText}</p>
+               <div class="interactive.textEntry">
+                       <textEntryInteraction responseIdentifier="{$questionid}" expectedLength="200"></textEntryInteraction>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+</assessmentItem>
diff --git a/question/format/qti_two/templates/numerical.tpl b/question/format/qti_two/templates/numerical.tpl
new file mode 100755 (executable)
index 0000000..d15992e
--- /dev/null
@@ -0,0 +1,65 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="single" baseType="float">
+               <correctResponse>
+                       <value>{$answer->answer}</value>
+               </correctResponse>
+               <mapping defaultValue="0">
+                       <mapEntry mapKey="{$answer->answer}" mappedValue="{$answer->fraction}" />
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
+       <itemBody>
+               <p>{$questionText}</p>
+               <div class="interactive.textEntry">
+            <textEntryInteraction responseIdentifier="{$questionid}" expectedLength="10"/>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+                       <responseIf>
+                               <and>
+                               <not>
+                                       <isNull>
+                                               <variable identifier="{$questionid}" />
+                                       </isNull>
+                               </not>
+                               <gte>
+                                       <baseValue baseType="float">{$lowerbound}</baseValue>
+                                       <variable identifier="{$questionid}" />
+                               </gte>
+                               <lte>
+                                       <baseValue baseType="float">{$upperbound}</baseValue>
+                                       <variable identifier="{$questionid}" />
+                               </lte>
+                               </and>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="integer">1</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="integer">0</baseValue>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+        <setOutcomeValue identifier="FEEDBACK">
+            <variable identifier="{$questionid}"/>
+        </setOutcomeValue>             
+       </responseProcessing>
+{if $answer->feedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$answer->id}" showHide="show">{$answer->feedback}</modalFeedback>
+{/if}
+{if $answer->altfeedback != ''}
+       <modalFeedback outcomeIdentifier="FEEDBACK" identifier="{$answer->id}" showHide="hide">{$answer->altfeedback}</modalFeedback>
+{/if}
+</assessmentItem>
diff --git a/question/format/qti_two/templates/textEntry.tpl b/question/format/qti_two/templates/textEntry.tpl
new file mode 100755 (executable)
index 0000000..6c03937
--- /dev/null
@@ -0,0 +1,50 @@
+{if $courselevelexport}<?xml version="1.0" encoding="UTF-8"?>{/if}
+<assessmentItem xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ./imsqti_item_v2p0.xsd" identifier="{$assessmentitemidentifier}" title="{$assessmentitemtitle}" adaptive="false" timeDependent="false">
+       <responseDeclaration identifier="{$questionid}" cardinality="{$responsedeclarationcardinality}" baseType="string">
+               <correctResponse>
+               {section name=answer loop=$correctresponses}
+                       <value>{$correctresponses[answer].answer}</value>
+               {/section}
+               </correctResponse>
+               <mapping lowerBound="0" upperBound="1" defaultValue="0">
+               {section name=answer loop=$answers}
+                   {if $answers[answer].fraction != 0}
+                       <mapEntry mapKey="{$answers[answer].answer}" mappedValue="{$answers[answer].fraction}" />
+                       {/if}
+               {/section}
+               </mapping>
+       </responseDeclaration>
+       <outcomeDeclaration identifier="SCORE" cardinality="single" baseType="float"/>
+       <itemBody>
+               <p>{$questionText}</p>
+               <div class="interactive.textEntry">
+            <textEntryInteraction responseIdentifier="{$questionid}" expectedLength="15"/>
+               </div>
+       {if $question_has_image == 1}
+               <div class="media">
+           {if $hassize == 1}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" width="{$question->mediax}" height="{$question->mediay}" />
+               {else}
+                       <object type="{$question->mediamimetype}" data="{$question->mediaurl}" />     
+               {/if}
+               </div>
+       {/if}
+       </itemBody>
+       <responseProcessing xmlns="http://www.imsglobal.org/xsd/imsqti_item_v2p0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsglobal.org/xsd/imsqti_item_v2p0 ../imsqti_item_v2p0.xsd">
+               <responseCondition>
+                       <responseIf>
+                               <isNull>
+                                       <variable identifier="{$questionid}"/>
+                               </isNull>
+                               <setOutcomeValue identifier="SCORE">
+                                       <baseValue baseType="integer">0</baseValue>
+                               </setOutcomeValue>
+                       </responseIf>
+                       <responseElse>
+                               <setOutcomeValue identifier="SCORE">
+                                       <mapResponse identifier="{$questionid}"/>
+                               </setOutcomeValue>
+                       </responseElse>
+               </responseCondition>
+       </responseProcessing>
+</assessmentItem>