MDL-30001 WEBCT Import does not work for 2.0
authorJean-Michel Vedrine <vedrine@vedrine.org>
Sun, 28 Oct 2012 14:36:04 +0000 (15:36 +0100)
committerJean-Michel Vedrine <vedrine@vedrine.org>
Thu, 8 Aug 2013 05:43:43 +0000 (07:43 +0200)
question/format/webct/TODO.txt [deleted file]
question/format/webct/format.php
question/format/webct/lang/en/qformat_webct.php
question/format/webct/tests/fixtures/sample_webct.txt [new file with mode: 0644]
question/format/webct/tests/webctformat_test.php [new file with mode: 0644]
question/format/webct/version.php

diff --git a/question/format/webct/TODO.txt b/question/format/webct/TODO.txt
deleted file mode 100644 (file)
index 109156c..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-- Ajouter un espace après un symbole <= s'il préfixe une parenthèse ouvrante.
-- Gérer 'and', 'or', ... comme &&, ||
-- Ne pas mettre de '$' sur les @param si aucun nom de variable n'est spécifié.
-- Ajouter OneTrueBrace pour les if, while, for, else ... (Gustavo Carreno <gcarreno@netvisao.pt>,
-Richard Bateman <richard@randyb.byu.edu>, Yaroslav Shvetsov <yaro@totaldesign.ru>, Chris Small <chris@owta.net>)
-- Remove blank lines (Sergio Marchesini <smarques@tin.it>)
-- Forcer une ligne vide après une déclaration de fonction (Richard Bateman <richard@randyb.byu.edu>)
index c7424a6..9bc4f3a 100644 (file)
@@ -105,7 +105,7 @@ function qformat_webct_convert_formula($formula) {
             for ($i = 1; $deep; ++$i) {
                 if (!preg_match('~^(.*[^[:alnum:]_])?([[:alnum:]_]*([)(])([^)(]*[)(]){'.$i.'})$~',
                         $splits[0], $regs)) {
-                    print_error("parenthesisinproperstart", 'question', '', $splits[0]);
+                    print_error('parenthesisinproperstart', 'question', '', $splits[0]);
                 }
                 if ('(' == $regs[3]) {
                     --$deep;
@@ -135,14 +135,14 @@ function qformat_webct_convert_formula($formula) {
             for ($i = 1; $deep; ++$i) {
                 if (!preg_match('~^([+-]?[[:alnum:]_]*([)(][^)(]*){'.$i.'}([)(]))(.*)~',
                         $splits[1], $regs)) {
-                    print_error("parenthesisinproperclose", 'question', '', $splits[1]);
+                    print_error('parenthesisinproperclose', 'question', '', $splits[1]);
                 }
                 if (')' == $regs[3]) {
                     --$deep;
                 } else if ('(' == $regs[3]) {
                     ++$deep;
                 } else {
-                    print_error("impossiblechar", 'question');
+                    print_error('impossiblechar', 'question');
                 }
             }
             $exp = $regs[1];
@@ -166,12 +166,173 @@ function qformat_webct_convert_formula($formula) {
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qformat_webct extends qformat_default {
+    /** @var string path to the temporary directory. */
+    public $tempdir = '';
 
+    /**
+     * This plugin provide import
+     * @return bool true
+     */
     public function provide_import() {
         return true;
     }
 
-    protected function readquestions($lines) {
+    public function can_import_file($file) {
+        $mimetypes = array(
+            mimeinfo('type', '.txt'),
+            mimeinfo('type', '.zip')
+        );
+        return in_array($file->get_mimetype(), $mimetypes);
+    }
+
+    public function mime_type() {
+        return mimeinfo('type', '.zip');
+    }
+
+    /**
+     * Store an image file in a draft filearea
+     * @param array $text, if itemid element don't exists it will be created
+     * @param string tempdir path to root of image tree
+     * @param string filepathinsidetempdir path to image in the tree
+     * @param string filename image's name
+     * @return string new name of the image as it was stored
+     */
+    protected function store_file_for_text_field(&$text, $tempdir, $filepathinsidetempdir, $filename) {
+        global $USER;
+        $fs = get_file_storage();
+        if (empty($text['itemid'])) {
+            $text['itemid'] = file_get_unused_draft_itemid();
+        }
+        // As question file areas don't support subdirs,
+        // convert path to filename.
+        // So that images with same name can be imported.
+        $newfilename = clean_param(str_replace('/', '__', $filepathinsidetempdir . '__' . $filename), PARAM_FILE);
+        $filerecord = array(
+            'contextid' => context_user::instance($USER->id)->id,
+            'component' => 'user',
+            'filearea'  => 'draft',
+            'itemid'    => $text['itemid'],
+            'filepath'  => '/',
+            'filename'  => $newfilename,
+        );
+        $fs->create_file_from_pathname($filerecord, $tempdir . '/' . $filepathinsidetempdir . '/' . $filename);
+        return $newfilename;
+    }
+
+    /**
+     * Given an HTML text with references to images files,
+     * store all images in a draft filearea,
+     * and return an array with all urls in text recoded,
+     * format set to FORMAT_HTML, and itemid set to filearea itemid
+     * @param string text text to parse and recode
+     * @return array with keys text, format, itemid.
+     */
+    public function text_field($text) {
+        $data = array();
+        // Step one, find all file refs then add to array.
+        preg_match_all('|<img[^>]+src="([^"]*)"|i', $text, $out); // Find all src refs.
+
+        $filepaths = array();
+        foreach ($out[1] as $path) {
+            $fullpath = $this->tempdir . '/' . $path;
+            if (is_readable($fullpath) && !in_array($path, $filepaths)) {
+                $dirpath = dirname($path);
+                $filename = basename($path);
+                $newfilename = $this->store_file_for_text_field($data, $this->tempdir, $dirpath, $filename);
+                $text = preg_replace("|$path|", "@@PLUGINFILE@@/" . $newfilename, $text);
+                $filepaths[] = $path;
+            }
+
+        }
+        $data['text'] = $text;
+        $data['format'] = FORMAT_HTML;
+        return $data;
+    }
+
+    /**
+     * Does any post-processing that may be desired
+     * Clean the temporary directory if a zip file was imported
+     * @return bool success
+     */
+    public function importpostprocess() {
+        if ($this->tempdir != '') {
+            fulldelete($this->tempdir);
+        }
+        return true;
+    }
+
+    /**
+     * Return content of all files containing questions,
+     * as an array one element for each file found,
+     * For each file, the corresponding element is an array of lines.
+     * @param string filename name of file
+     * @return mixed contents array or false on failure
+     */
+    public function readdata($filename) {
+
+        // Find if we are importing a .txt file.
+        if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) == 'txt') {
+            if (!is_readable($filename)) {
+                $this->error(get_string('filenotreadable', 'error'));
+                return false;
+            }
+            return file($filename);
+        }
+        // We are importing a zip file.
+        // Create name for temporary directory.
+        $uniquecode = time();
+        $this->tempdir = make_temp_directory('webct_import/' . $uniquecode);
+        if (is_readable($filename)) {
+            if (!copy($filename, $this->tempdir . '/webct.zip')) {
+                $this->error(get_string('cannotcopybackup', 'question'));
+                fulldelete($this->tempdir);
+                return false;
+            }
+            if (unzip_file($this->tempdir . '/webct.zip', '', false)) {
+                $dir = $this->tempdir;
+                if ((($handle = opendir($dir))) == false) {
+                    // The directory could not be opened.
+                    fulldelete($this->tempdir);
+                    return false;
+                }
+                // Create arrays to store files and directories.
+                $dirfiles = array();
+                $dirsubdirs = array();
+                $slash = '/';
+
+                // Loop through all directory entries, and construct two temporary arrays containing files and sub directories.
+                while (false !== ($entry = readdir($handle))) {
+                    if (is_dir($dir. $slash .$entry) && $entry != '..' && $entry != '.') {
+                        $dirsubdirs[] = $dir. $slash .$entry;
+                    } else if ($entry != '..' && $entry != '.') {
+                        $dirfiles[] = $dir. $slash .$entry;
+                    }
+                }
+                if ((($handle = opendir($dirsubdirs[0]))) == false) {
+                    // The directory could not be opened.
+                    fulldelete($this->tempdir);
+                    return false;
+                }
+                while (false !== ($entry = readdir($handle))) {
+                    if (is_dir($dirsubdirs[0]. $slash .$entry) && $entry != '..' && $entry != '.') {
+                        $dirsubdirs[] = $dirsubdirs[0]. $slash .$entry;
+                    } else if ($entry != '..' && $entry != '.') {
+                        $dirfiles[] = $dirsubdirs[0]. $slash .$entry;
+                    }
+                }
+                return file($dirfiles[1]);
+            } else {
+                $this->error(get_string('cannotunzip', 'question'));
+                fulldelete($this->temp_dir);
+            }
+        } else {
+            $this->error(get_string('cannotreaduploadfile', 'error'));
+            fulldelete($this->tempdir);
+        }
+        return false;
+    }
+
+    public function readquestions ($lines) {
         $webctnumberregex =
                 '[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)((e|E|\\*10\\*\\*)([+-]?[0-9]+|\\([+-]?[0-9]+\\)))?';
 
@@ -190,12 +351,17 @@ class qformat_webct extends qformat_default {
 
         foreach ($lines as $line) {
             $nlinecounter++;
-            $line = iconv("Windows-1252", "UTF-8", $line);
+            $line = textlib::convert($line, 'windows-1252', 'utf-8');
             // Processing multiples lines strings.
 
             if (isset($questiontext) and is_string($questiontext)) {
                 if (preg_match("~^:~", $line)) {
-                    $question->questiontext = trim($questiontext);
+                    $questiontext = $this->text_field(trim($questiontext));
+                    $question->questiontext = $questiontext['text'];
+                    $question->questiontextformat = $questiontext['format'];
+                    if (isset($questiontext['itemid'])) {
+                        $question->questiontextitemid = $questiontext['itemid'];
+                    }
                     unset($questiontext);
                 } else {
                     $questiontext .= str_replace('\:', ':', $line);
@@ -206,8 +372,14 @@ class qformat_webct extends qformat_default {
             if (isset($answertext) and is_string($answertext)) {
                 if (preg_match("~^:~", $line)) {
                     $answertext = trim($answertext);
-                    $question->answer[$currentchoice] = $answertext;
-                    $question->subanswers[$currentchoice] = $answertext;
+                    if ($question->qtype == 'multichoice' || $question->qtype == 'match' ) {
+                        $question->answer[$currentchoice] = $this->text_field($answertext);
+                        $question->subanswers[$currentchoice] = $question->answer[$currentchoice];
+
+                    } else {
+                        $question->answer[$currentchoice] = $answertext;
+                        $question->subanswers[$currentchoice] = $answertext;
+                    }
                     unset($answertext);
                 } else {
                     $answertext .= str_replace('\:', ':', $line);
@@ -227,7 +399,7 @@ class qformat_webct extends qformat_default {
 
             if (isset($feedbacktext) and is_string($feedbacktext)) {
                 if (preg_match("~^:~", $line)) {
-                    $question->feedback[$currentchoice] = trim($feedbacktext);
+                    $question->feedback[$currentchoice] = $this->text_field(trim($feedbacktext));
                     unset($feedbacktext);
                 } else {
                     $feedbacktext .= str_replace('\:', ':', $line);
@@ -237,7 +409,7 @@ class qformat_webct extends qformat_default {
 
             if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) {
                 if (preg_match("~^:~", $line)) {
-                    $question->tempgeneralfeedback= trim($generalfeedbacktext);
+                    $question->tempgeneralfeedback = trim($generalfeedbacktext);
                     unset($generalfeedbacktext);
                 } else {
                     $generalfeedbacktext .= str_replace('\:', ':', $line);
@@ -245,6 +417,17 @@ class qformat_webct extends qformat_default {
                 }
             }
 
+            if (isset($graderinfo) and is_string($graderinfo)) {
+                if (preg_match("~^:~", $line)) {
+                    $question->graderinfo['text'] = trim($graderinfo);
+                    $question->graderinfo['format'] = FORMAT_HTML;
+                    unset($graderinfo);
+                } else {
+                    $graderinfo .= str_replace('\:', ':', $line);
+                    continue;
+                }
+            }
+
             $line = trim($line);
 
             if (preg_match("~^:(TYPE|EOF):~i", $line)) {
@@ -260,7 +443,7 @@ class qformat_webct extends qformat_default {
                         $question->defaultmark = 1;
                     }
                     if (!isset($question->image)) {
-                        $question->image = "";
+                        $question->image = '';
                     }
 
                     // Perform sanity checks.
@@ -276,19 +459,25 @@ class qformat_webct extends qformat_default {
                         // Create empty feedback array.
                         foreach ($question->answer as $key => $dataanswer) {
                             if (!isset($question->feedback[$key])) {
-                                $question->feedback[$key] = '';
+                                $question->feedback[$key]['text'] = '';
+                                $question->feedback[$key]['format'] = FORMAT_HTML;
                             }
                         }
                         // This tempgeneralfeedback allows the code to work with versions from 1.6 to 1.9.
                         // When question->generalfeedback is undefined, the webct feedback is added to each answer feedback.
                         if (isset($question->tempgeneralfeedback)) {
                             if (isset($question->generalfeedback)) {
-                                $question->generalfeedback = $question->tempgeneralfeedback;
+                                $generalfeedback = $this->text_field($question->tempgeneralfeedback);
+                                $question->generalfeedback = $generalfeedback['text'];
+                                $question->generalfeedbackformat = $generalfeedback['format'];
+                                if (isset($generalfeedback['itemid'])) {
+                                    $question->genralfeedbackitemid = $generalfeedback['itemid'];
+                                }
                             } else {
                                 foreach ($question->answer as $key => $dataanswer) {
-                                    if ($question->tempgeneralfeedback !='') {
-                                        $question->feedback[$key] = $question->tempgeneralfeedback
-                                                .'<br/>'.$question->feedback[$key];
+                                    if ($question->tempgeneralfeedback != '') {
+                                        $question->feedback[$key]['text'] = $question->tempgeneralfeedback
+                                                .'<br/>'.$question->feedback[$key]['text'];
                                     }
                                 }
                             }
@@ -315,6 +504,8 @@ class qformat_webct extends qformat_default {
                                 break;
 
                             case 'multichoice':
+                                $question = $this->add_blank_combined_feedback($question);
+
                                 if ($question->single) {
                                     if ($maxfraction != 1) {
                                         $maxfraction = $maxfraction * 100;
@@ -341,7 +532,7 @@ class qformat_webct extends qformat_default {
                                     }
                                 }
                                 foreach ($question->dataset as $dataset) {
-                                    $dataset->itemcount=count($dataset->datasetitem);
+                                    $dataset->itemcount = count($dataset->datasetitem);
                                 }
                                 $question->import_process = true;
                                 unset($question->answer); // Not used in calculated question.
@@ -349,6 +540,7 @@ class qformat_webct extends qformat_default {
                             case 'match':
                                 // MDL-10680:
                                 // Switch subquestions and subanswers.
+                                $question = $this->add_blank_combined_feedback($question);
                                 foreach ($question->subquestions as $id => $subquestion) {
                                     $temp = $question->subquestions[$id];
                                     $question->subquestions[$id] = $question->subanswers[$id];
@@ -438,10 +630,26 @@ class qformat_webct extends qformat_default {
             }
 
             if (preg_match("~^:TYPE:P~i", $line)) {
-                // Paragraph question (essay).
-                $warnings[] = get_string("paragraphquestion", "qformat_webct", $nlinecounter);
-                unset($question);
-                $ignorerestofquestion = true;         // Question Type not handled by Moodle.
+                // Paragraph Question.
+                $question = $this->defaultquestion();
+                $question->qtype = 'essay';
+                $question->responseformat = 'editor';
+                $question->responsefieldlines = 15;
+                $question->attachments = 0;
+                $question->graderinfo = array(
+                        'text' => '',
+                        'format' => FORMAT_HTML,
+                    );
+                $question->feedback = array();
+                $question->generalfeedback = '';
+                $question->generalfeedbackformat = FORMAT_HTML;
+                $question->generalfeedbackfiles = array();
+                $question->responsetemplate = $this->text_field('');
+                $question->questiontextformat = FORMAT_HTML;
+                $ignorerestofquestion = false;
+                // To make us pass the end-of-question sanity checks.
+                $question->answer = array('dummy');
+                $question->fraction = array('1.0');
                 continue;
             }
 
@@ -503,21 +711,26 @@ class qformat_webct extends qformat_default {
 
             $bishtmltext = preg_match("~:H$~i", $line);  // True if next lines are coded in HTML.
             if (preg_match("~^:QUESTION~i", $line)) {
-                $questiontext='';               // Start gathering next lines.
+                $questiontext = '';               // Start gathering next lines.
                 continue;
             }
 
             if (preg_match("~^:ANSWER([0-9]+):([^:]+):([0-9\.\-]+):(.*)~i", $line, $webctoptions)) { // Shortanswer.
-                $currentchoice=$webctoptions[1];
-                $answertext=$webctoptions[2];            // Start gathering next lines.
-                $question->fraction[$currentchoice]=($webctoptions[3]/100);
+                $currentchoice = $webctoptions[1];
+                $answertext = $webctoptions[2];            // Start gathering next lines.
+                $question->fraction[$currentchoice] = ($webctoptions[3]/100);
                 continue;
             }
 
             if (preg_match("~^:ANSWER([0-9]+):([0-9\.\-]+)~i", $line, $webctoptions)) {
-                $answertext='';                 // Start gathering next lines.
-                $currentchoice=$webctoptions[1];
-                $question->fraction[$currentchoice]=($webctoptions[2]/100);
+                $answertext = '';                 // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
+                $question->fraction[$currentchoice] = ($webctoptions[2]/100);
+                continue;
+            }
+
+            if (preg_match('~^:ANSWER:~i', $line)) { // Essay.
+                $graderinfo  = '';      // Start gathering next lines.
                 continue;
             }
 
@@ -531,7 +744,8 @@ class qformat_webct extends qformat_default {
                 $question->fraction[$currentchoice] = 1.0;
                 $question->tolerance[$currentchoice] = 0.0;
                 $question->tolerancetype[$currentchoice] = 2; // Nominal (units in webct).
-                $question->feedback[$currentchoice] = '';
+                $question->feedback[$currentchoice]['text'] = '';
+                $question->feedback[$currentchoice]['format'] = FORMAT_HTML;
                 $question->correctanswerlength[$currentchoice] = 4;
 
                 $datasetnames =
@@ -547,30 +761,30 @@ class qformat_webct extends qformat_default {
             }
 
             if (preg_match("~^:L([0-9]+)~i", $line, $webctoptions)) {
-                $answertext='';                 // Start gathering next lines.
-                $currentchoice=$webctoptions[1];
-                $question->fraction[$currentchoice]=1;
+                $answertext = '';                 // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
+                $question->fraction[$currentchoice] = 1;
                 continue;
             }
 
             if (preg_match("~^:R([0-9]+)~i", $line, $webctoptions)) {
-                $responsetext='';                // Start gathering next lines.
-                $currentchoice=$webctoptions[1];
+                $responsetext = '';                // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
 
             if (preg_match("~^:REASON([0-9]+):?~i", $line, $webctoptions)) {
-                $feedbacktext='';               // Start gathering next lines.
-                $currentchoice=$webctoptions[1];
+                $feedbacktext = '';               // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
             if (preg_match("~^:FEEDBACK([0-9]+):?~i", $line, $webctoptions)) {
-                $generalfeedbacktext='';               // Start gathering next lines.
-                $currentchoice=$webctoptions[1];
+                $generalfeedbacktext = '';               // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
             if (preg_match('~^:FEEDBACK:(.*)~i', $line, $webctoptions)) {
-                $generalfeedbacktext='';               // Start gathering next lines.
+                $generalfeedbacktext = '';               // Start gathering next lines.
                 continue;
             }
             if (preg_match('~^:LAYOUT:(.*)~i', $line, $webctoptions)) {
@@ -630,11 +844,11 @@ class qformat_webct extends qformat_default {
             }
 
             if (isset($question->qtype )&& 'calculated' == $question->qtype && preg_match('~^:ANSTYPE:dec~i', $line)) {
-                $question->correctanswerformat[$currentchoice]='1';
+                $question->correctanswerformat[$currentchoice] = '1';
                 continue;
             }
             if (isset($question->qtype )&& 'calculated' == $question->qtype && preg_match('~^:ANSTYPE:sig~i', $line)) {
-                $question->correctanswerformat[$currentchoice]='2';
+                $question->correctanswerformat[$currentchoice] = '2';
                 continue;
             }
         }
index ac33e23..f79a98e 100644 (file)
 $string['errorsdetected'] = '{$a} error(s) detected';
 $string['missinganswer'] = 'Too few :ANSWER, :Lx, :Rx statements for question line {$a}. You must define at last 2 possible answers';
 $string['missingquestion'] = 'Missing question label after line {$a}';
-$string['paragraphquestion'] = 'Paragraph question';
 $string['pluginname'] = 'WebCT format';
 $string['pluginname_help'] = 'WebCT format enables multiple-choice and short answer questions saved in WebCT\'s text-based format to be imported.';
 $string['pluginname_link'] = 'qformat/webct';
 $string['questionnametoolong'] = 'Question name too long at line {$a} (255 char. max). It has been truncated.';
-$string['unknowntype'] = 'Unknown type';
+$string['unknowntype'] = 'Unknown question type after line {$a}';
 $string['warningsdetected'] = '{$a} warning(s) detected';
 $string['wronggrade'] = 'Wrong grade (after line {$a}) :';
diff --git a/question/format/webct/tests/fixtures/sample_webct.txt b/question/format/webct/tests/fixtures/sample_webct.txt
new file mode 100644 (file)
index 0000000..e94efe9
--- /dev/null
@@ -0,0 +1,117 @@
+# Start of question:Question 001
+:TYPE:MC:1:0:A
+:TITLE:USER-1
+:QUESTION:H
+42 is the Absolute Answer to everything.
+:LAYOUT:horizontal
+:ANSWERORDER:randomized
+:INDICES:letters
+:ANSWER1:0:H
+True
+:REASON1:H
+42 is the <b>Ultimate</b> Answer.
+:ANSWER2:100:H
+False
+:REASON2:H
+42 is the <b>Ultimate</b> Answer.
+:CAT:webct
+:ASSESSMENT:Test
+# End of question:Question 001
+
+# Start of question:Question 002
+:TYPE:MC:1:0:A
+:TITLE:USER-2
+:QUESTION:H
+<font size="+1">What's between orange and green in the spectrum?</font>
+:LAYOUT:horizontal
+:ANSWERORDER:randomized
+:INDICES:letters
+:ANSWER1:0:H
+red
+:REASON1:H
+Red is not between orange and green in the spectrum but yellow is.
+:ANSWER2:100:H
+yellow
+:REASON2:H
+You gave the right answer.
+:ANSWER3:0:H
+blue
+:REASON3:H
+Blue is not between orange and green in the spectrum but yellow is.
+:CAT:webct
+:ASSESSMENT:Test
+# End of question:Question 002
+
+# Start of question:Question 003
+:TYPE:MC:N:0:A
+:TITLE:USER-3
+:QUESTION:H
+<i>What's between orange and green in the spectrum?</i>
+:LAYOUT:horizontal
+:ANSWERORDER:randomized
+:INDICES:letters
+:ANSWER1:50:H
+yellow
+:REASON1:H
+True, yellow is between orange and green in the spectrum,
+:ANSWER2:0:H
+red
+:REASON2:H
+False, red is not between orange and green in the spectrum,
+:ANSWER3:50:H
+off-beige
+:REASON3:H
+True, off-beige is between orange and green in the spectrum,
+:ANSWER4:0:H
+blue
+:REASON4:H
+False, red is not between orange and green in the spectrum,
+:CAT:webct
+:ASSESSMENT:Test
+# End of question:Question 003
+
+# Start of question:Question 004
+:TYPE:M:short:short:E:0
+:TITLE:Classify the animals.
+:QUESTION:H
+<i>Classify the animals.</i>
+:IMAGE:
+:L1
+cat
+:R1
+mammal
+:L2
+frog
+:R2
+amphibian
+:L3
+newt
+:R3
+amphibian
+:CAT:Ch 00 Instr Test Items
+:ASSESSMENT:Test
+# End of question:Question 004
+
+# Start of question:Question 005
+:TYPE:S
+:TITLE:USER-5
+:QUESTION:H
+Name an amphibian&#58; __________
+:ANSWERS:1
+:ANSWER1:frog:100:0:20:0
+:FEEDBACK1:H
+A frog is an amphibian
+:CAT:webct
+:ASSESSMENT:Test
+# End of question:Question 005
+
+# Start of question:Question 006
+:TYPE:P
+:TITLE:USER-6
+:QUESTION:H:60:5
+How are you?
+:ANSWER:H
+Blackboard answer for essay questions will be imported as informations for graders.
+:CAT:webct
+:ASSESSMENT:Test
+# End of question:Question 006
diff --git a/question/format/webct/tests/webctformat_test.php b/question/format/webct/tests/webctformat_test.php
new file mode 100644 (file)
index 0000000..12783aa
--- /dev/null
@@ -0,0 +1,366 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unit tests for Web CT question importer.
+ *
+ * @package    qformat_webct
+ * @copyright  2013 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->dirroot . '/question/format.php');
+require_once($CFG->dirroot . '/question/format/webct/format.php');
+require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
+
+
+/**
+ * Unit tests for the webct question import format.
+ *
+ * @copyright  2013 Jean-Michel Vedrine
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qformat_webct_test extends question_testcase {
+
+    public function make_test() {
+        $lines = file(__DIR__ . '/fixtures/sample_webct.txt');
+        return $lines;
+    }
+
+    public function test_import_match() {
+
+        $txt = $this->make_test();
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[3];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'match';
+        $expectedq->name = 'Classify the animals.';
+        $expectedq->questiontext = '<i>Classify the animals.</i>';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+                );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_MOODLE;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->subquestions = array(
+            1 => array('text' => 'cat', 'format' => FORMAT_HTML),
+            2 => array('text' => 'frog', 'format' => FORMAT_HTML),
+            3 => array('text' => 'newt', 'format' => FORMAT_HTML));
+        $expectedq->subanswers = array(1 => 'mammal', 2 => 'amphibian', 3 => 'amphibian');
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_single() {
+
+        $txt = $this->make_test();
+
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[1];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 1;
+        $expectedq->name = 'USER-2';
+        $expectedq->questiontext = '<font size="+1">What\'s between orange and green in the spectrum?</font>';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+                );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_MOODLE;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                1 => array(
+                    'text' => 'red',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'yellow',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => 'blue',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(
+                1 => 0,
+                2 => 1,
+                3 => 0,
+            );
+        $expectedq->feedback = array(
+                1 => array(
+                    'text' => 'Red is not between orange and green in the spectrum but yellow is.',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'You gave the right answer.',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => 'Blue is not between orange and green in the spectrum but yellow is.',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_multichoice_multi() {
+
+        $txt = $this->make_test();
+
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[2];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 0;
+        $expectedq->name = 'USER-3';
+        $expectedq->questiontext = '<i>What\'s between orange and green in the spectrum?</i>';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+                );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_MOODLE;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->penalty = 0.3333333;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                1 => array(
+                    'text' => 'yellow',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'red',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => 'off-beige',
+                    'format' => FORMAT_HTML,
+                ),
+                4 => array(
+                    'text' => 'blue',
+                    'format' => FORMAT_HTML,
+                )
+            );
+        $expectedq->fraction = array(
+                1 => 0.5,
+                2 => 0,
+                3 => 0.5,
+                4 => 0,
+            );
+        $expectedq->feedback = array(
+                1 => array(
+                    'text' => 'True, yellow is between orange and green in the spectrum,',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'False, red is not between orange and green in the spectrum,',
+                    'format' => FORMAT_HTML,
+                ),
+                3 => array(
+                    'text' => 'True, off-beige is between orange and green in the spectrum,',
+                    'format' => FORMAT_HTML,
+                ),
+                4 => array(
+                    'text' => 'False, red is not between orange and green in the spectrum,',
+                    'format' => FORMAT_HTML,
+                )
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_truefalse() {
+
+        $txt = $this->make_test();
+
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[0];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'multichoice';
+        $expectedq->single = 1;
+        $expectedq->name = 'USER-1';
+        $expectedq->questiontext = '42 is the Absolute Answer to everything.';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->correctfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->partiallycorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+            );
+        $expectedq->incorrectfeedback = array(
+                'text' => '',
+                'format' => FORMAT_HTML,
+                'files' => array(),
+                );
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_MOODLE;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->shuffleanswers = get_config('quiz', 'shuffleanswers');
+        $expectedq->answer = array(
+                1 => array(
+                    'text' => 'True',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => 'False',
+                    'format' => FORMAT_HTML,
+                ),
+            );
+        $expectedq->fraction = array(
+                1 => 0,
+                2 => 1,
+            );
+        $expectedq->feedback = array(
+                1 => array(
+                    'text' => '42 is the <b>Ultimate</b> Answer.',
+                    'format' => FORMAT_HTML,
+                ),
+                2 => array(
+                    'text' => '42 is the <b>Ultimate</b> Answer.',
+                    'format' => FORMAT_HTML,
+                ),
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_fill_in_the_blank() {
+
+        $txt = $this->make_test();
+
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[4];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'shortanswer';
+        $expectedq->name = 'USER-5';
+        $expectedq->questiontext = 'Name an amphibian&#58; __________';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = 'A frog is an amphibian';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->usecase = 0;
+        $expectedq->answer = array(
+                1 => 'frog',
+            );
+        $expectedq->fraction = array(
+                1 => 1,
+            );
+        $expectedq->feedback = array(
+                1 => array(
+                    'text' => '',
+                    'format' => FORMAT_HTML,
+                ),
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+
+    public function test_import_essay() {
+
+        $txt = $this->make_test();
+
+        $importer = new qformat_webct();
+        $questions = $importer->readquestions($txt);
+        $q = $questions[5];
+
+        $expectedq = new stdClass();
+        $expectedq->qtype = 'essay';
+        $expectedq->name = 'USER-6';
+        $expectedq->questiontext = 'How are you?';
+        $expectedq->questiontextformat = FORMAT_HTML;
+        $expectedq->generalfeedback = '';
+        $expectedq->generalfeedbackformat = FORMAT_HTML;
+        $expectedq->defaultmark = 1;
+        $expectedq->length = 1;
+        $expectedq->responseformat = 'editor';
+        $expectedq->responsefieldlines = 15;
+        $expectedq->attachments = 0;
+        $expectedq->graderinfo = array(
+                'text' => 'Blackboard answer for essay questions will be imported as informations for graders.',
+                'format' => FORMAT_HTML,
+            );
+
+        $this->assert(new question_check_specified_fields_expectation($expectedq), $q);
+    }
+}
index e86d51f..527faf8 100644 (file)
@@ -15,7 +15,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Version information for the calculated question type.
+ * Version information for the Web CT question import format.
  *
  * @package    qformat_webct
  * @copyright  2011 The Open University
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component = 'qformat_webct';
-$plugin->version   = 2013050100;
+$plugin->version   = 2013050101;
 
 $plugin->requires  = 2013050100;