diff --git a/question/format/webct/TODO.txt b/question/format/webct/TODO.txt deleted file mode 100644 index 6471b6b4917..00000000000 --- a/question/format/webct/TODO.txt +++ /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'esp sp�cifi�. -- Ajouter OneTrueBrace pour les if, while, for, else ... (Gustavo Carreno , Richard Bateman , Yaroslav Shvetsov , Chris Small ) -- Remove blank lines (Sergio Marchesini ) -- Forcer une ligne vide apr�s une d�claration de fonction (Richard Bateman ) - diff --git a/question/format/webct/format.php b/question/format/webct/format.php index 8692542e13b..7f36acccea0 100644 --- a/question/format/webct/format.php +++ b/question/format/webct/format.php @@ -17,8 +17,7 @@ /** * Web CT question importer. * - * @package qformat - * @subpackage webct + * @package qformat_webct * @copyright 2004 ASP Consulting http://www.asp-consulting.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -31,11 +30,11 @@ defined('MOODLE_INTERNAL') || die(); * @param string $string * @return string */ -function unhtmlentities($string){ - $search = array ("']*?>.*?'si", // remove javascript - "'<[\/\!]*?[^]*?>'si", // remove HTML tags - "'([\r\n])[\s]+'", // remove spaces - "'&(quot|#34);'i", // remove HTML entites +function unhtmlentities($string) { + $search = array ("']*?>.*?'si", // Remove javascript. + "'<[\/\!]*?[^]*?>'si", // Remove HTML tags. + "'([\r\n])[\s]+'", // Remove spaces. + "'&(quot|#34);'i", // Remove HTML entites. "'&(amp|#38);'i", "'&(lt|#60);'i", "'&(gt|#62);'i", @@ -44,7 +43,7 @@ function unhtmlentities($string){ "'&(cent|#162);'i", "'&(pound|#163);'i", "'&(copy|#169);'i", - "'&#(\d+);'e"); // Evaluate like PHP + "'&#(\d+);'e"); // Evaluate like PHP. $replace = array ("", "", "\\1", @@ -67,46 +66,46 @@ function unhtmlentities($string){ */ function qformat_webct_convert_formula($formula) { - // Remove empty space, as it would cause problems otherwise: + // Remove empty space, as it would cause problems otherwise. $formula = str_replace(' ', '', $formula); - // Remove paranthesis after e,E and *10**: + // Remove paranthesis after e,E and *10**. while (preg_match('~[0-9.](e|E|\\*10\\*\\*)\\([+-]?[0-9]+\\)~', $formula, $regs)) { $formula = str_replace( $regs[0], preg_replace('/[)(]/', '', $regs[0]), $formula); } - // Replace *10** with e where possible + // Replace *10** with e where possible. while (preg_match('~(^[+-]?|[^eE][+-]|[^0-9eE+-])[0-9.]+\\*10\\*\\*[+-]?[0-9]+([^0-9.eE]|$)~', $formula, $regs)) { $formula = str_replace( $regs[0], str_replace('*10**', 'e', $regs[0]), $formula); } - // Replace other 10** with 1e where possible + // Replace other 10** with 1e where possible. while (preg_match('~(^|[^0-9.eE])10\\*\\*[+-]?[0-9]+([^0-9.eE]|$)~', $formula, $regs)) { $formula = str_replace( $regs[0], str_replace('10**', '1e', $regs[0]), $formula); } // Replace all other base**exp with the PHP equivalent function pow(base,exp) - // (Pretty tricky to exchange an operator with a function) + // (Pretty tricky to exchange an operator with a function). while (2 == count($splits = explode('**', $formula, 2))) { - // Find $base + // Find $base. if (preg_match('~^(.*[^0-9.eE])?(([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][+-]?[0-9]+)?|\\{[^}]*\\})$~', $splits[0], $regs)) { - // The simple cases + // The simple cases. $base = $regs[2]; $splits[0] = $regs[1]; } else if (preg_match('~\\)$~', $splits[0])) { - // Find the start of this parenthesis + // Find the start of this parenthesis. $deep = 1; - for ($i = 1 ; $deep ; ++$i) { + 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; @@ -123,27 +122,27 @@ function qformat_webct_convert_formula($formula) { print_error('badbase', 'question', '', $splits[0]); } - // Find $exp (similar to above but a little easier) + // Find $exp (similar to above but a little easier). if (preg_match('~^([+-]?(\\{[^}]\\}|([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][+-]?[0-9]+)?))(.*)~', $splits[1], $regs)) { - // The simple case + // The simple case. $exp = $regs[1]; $splits[1] = $regs[6]; } else if (preg_match('~^[+-]?[[:alnum:]_]*\\(~', $splits[1])) { - // Find the end of the parenthesis + // Find the end of the parenthesis. $deep = 1; - for ($i = 1 ; $deep ; ++$i) { + 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]; @@ -154,7 +153,7 @@ function qformat_webct_convert_formula($formula) { $formula = "$splits[0]pow($base,$exp)$splits[1]"; } - // Nothing more is known to need to be converted + // Nothing more is known to need to be converted. return $formula; } @@ -167,97 +166,275 @@ 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 = ''; - function provide_import() { - return true; + /** + * 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('|]+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]+\\)))?'; $questions = array(); $errors = array(); $warnings = array(); - $webct_options = array(); + $webctoptions = array(); - $ignore_rest_of_question = FALSE; + $ignorerestofquestion = false; - $nLineCounter = 0; - $nQuestionStartLine = 0; - $bIsHTMLText = FALSE; - $lines[] = ":EOF:"; // for an easiest processing of the last line - // $question = $this->defaultquestion(); + $nlinecounter = 0; + $nquestionstartline = 0; + $bishtmltext = false; + $lines[] = ":EOF:"; // For an easiest processing of the last line. + // We don't call defaultquestion() here, it will be called later. foreach ($lines as $line) { - $nLineCounter++; - $line = iconv("Windows-1252","UTF-8",$line); - // Processing multiples lines strings + $nlinecounter++; + $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); + if (preg_match("~^:~", $line)) { + $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 { + } else { $questiontext .= str_replace('\:', ':', $line); continue; } } if (isset($answertext) and is_string($answertext)) { - if (preg_match("~^:~",$line)) { + 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 { + } else { $answertext .= str_replace('\:', ':', $line); continue; } } if (isset($responsetext) and is_string($responsetext)) { - if (preg_match("~^:~",$line)) { + if (preg_match("~^:~", $line)) { $question->subquestions[$currentchoice] = trim($responsetext); unset($responsetext); - } - else { + } else { $responsetext .= str_replace('\:', ':', $line); continue; } } if (isset($feedbacktext) and is_string($feedbacktext)) { - if (preg_match("~^:~",$line)) { - $question->feedback[$currentchoice] = trim($feedbacktext); + if (preg_match("~^:~", $line)) { + $question->feedback[$currentchoice] = $this->text_field(trim($feedbacktext)); unset($feedbacktext); - } - else { + } else { $feedbacktext .= str_replace('\:', ':', $line); continue; } } if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) { - if (preg_match("~^:~",$line)) { - $question->tempgeneralfeedback= trim($generalfeedbacktext); + if (preg_match("~^:~", $line)) { + $question->tempgeneralfeedback = trim($generalfeedbacktext); unset($generalfeedbacktext); - } - else { + } else { $generalfeedbacktext .= str_replace('\:', ':', $line); continue; } } + 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)) { - // New Question or End of File - if (isset($question)) { // if previous question exists, complete, check and save it + if (preg_match("~^:(TYPE|EOF):~i", $line)) { + // New Question or End of File. + if (isset($question)) { // If previous question exists, complete, check and save it. - // Setup default value of missing fields + // Setup default value of missing fields. if (!isset($question->name)) { $question->name = $this->create_default_question_name( $question->questiontext, get_string('questionname', 'question')); @@ -266,35 +443,41 @@ class qformat_webct extends qformat_default { $question->defaultmark = 1; } if (!isset($question->image)) { - $question->image = ""; + $question->image = ''; } - // Perform sanity checks - $QuestionOK = TRUE; + // Perform sanity checks. + $questionok = true; if (strlen($question->questiontext) == 0) { - $warnings[] = get_string("missingquestion", "qformat_webct", $nQuestionStartLine); - $QuestionOK = FALSE; + $warnings[] = get_string('missingquestion', 'qformat_webct', $nquestionstartline); + $questionok = false; } - if (sizeof($question->answer) < 1) { // a question must have at least 1 answer - $errors[] = get_string("missinganswer", "qformat_webct", $nQuestionStartLine); - $QuestionOK = FALSE; - } - else { - // Create empty feedback array + if (count($question->answer) < 1) { // A question must have at least 1 answer. + $errors[] = get_string('missinganswer', 'qformat_webct', $nquestionstartline); + $questionok = false; + } else { + // Create empty feedback array. foreach ($question->answer as $key => $dataanswer) { - if(!isset( $question->feedback[$key])){ - $question->feedback[$key] = ''; + if (!isset($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)){ + // 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.'
'.$question->feedback[$key]; + if ($question->tempgeneralfeedback != '') { + $question->feedback[$key]['text'] = $question->tempgeneralfeedback + .'
'.$question->feedback[$key]['text']; } } } @@ -302,7 +485,7 @@ class qformat_webct extends qformat_default { } $maxfraction = -1; $totalfraction = 0; - foreach($question->fraction as $fraction) { + foreach ($question->fraction as $fraction) { if ($fraction > 0) { $totalfraction += $fraction; } @@ -314,24 +497,29 @@ class qformat_webct extends qformat_default { case 'shortanswer': if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; - $errors[] = "'$question->name': ".get_string("wronggrade", "qformat_webct", $nLineCounter).' '.get_string("fractionsnomax", "question", $maxfraction); - $QuestionOK = FALSE; + $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter) + .' '.get_string('fractionsnomax', 'question', $maxfraction); + $questionok = false; } break; case 'multichoice': + $question = $this->add_blank_combined_feedback($question); + if ($question->single) { if ($maxfraction != 1) { $maxfraction = $maxfraction * 100; - $errors[] = "'$question->name': ".get_string("wronggrade", "qformat_webct", $nLineCounter).' '.get_string("fractionsnomax", "question", $maxfraction); - $QuestionOK = FALSE; + $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter) + .' '.get_string('fractionsnomax', 'question', $maxfraction); + $questionok = false; } } else { - $totalfraction = round($totalfraction,2); + $totalfraction = round($totalfraction, 2); if ($totalfraction != 1) { $totalfraction = $totalfraction * 100; - $errors[] = "'$question->name': ".get_string("wronggrade", "qformat_webct", $nLineCounter).' '.get_string("fractionsaddwrong", "question", $totalfraction); - $QuestionOK = FALSE; + $errors[] = "'$question->name': ".get_string('wronggrade', 'qformat_webct', $nlinecounter) + .' '.get_string('fractionsaddwrong', 'question', $totalfraction); + $questionok = false; } } break; @@ -340,151 +528,166 @@ class qformat_webct extends qformat_default { foreach ($question->answers as $answer) { if ($formulaerror = qtype_calculated_find_formula_errors($answer)) { $warnings[] = "'$question->name': ". $formulaerror; - $QuestionOK = FALSE; + $questionok = false; } } 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 + $question->import_process = true; + unset($question->answer); // Not used in calculated question. break; case 'match': // MDL-10680: - // switch subquestions and subanswers - foreach ($question->subquestions as $id=>$subquestion) { + // 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]; $question->subanswers[$id] = $temp; } - if (count($question->answer) < 3){ - // add a dummy missing question - $question->name = 'Dummy question added '.$question->name ; + if (count($question->answer) < 3) { + // Add a dummy missing question. + $question->name = 'Dummy question added '.$question->name; $question->answer[] = 'dummy'; $question->subanswers[] = 'dummy'; $question->subquestions[] = 'dummy'; $question->fraction[] = '0.0'; $question->feedback[] = ''; - } - break; + } + break; default: - // No problemo + // No problemo. } } - if ($QuestionOK) { - // echo "
"; print_r ($question);
-                        $questions[] = $question;    // store it
-                        unset($question);            // and prepare a new one
+                    if ($questionok) {
+                        $questions[] = $question;    // Store it.
+                        unset($question);            // And prepare a new one.
                         $question = $this->defaultquestion();
                     }
                 }
-                $nQuestionStartLine = $nLineCounter;
+                $nquestionstartline = $nlinecounter;
             }
 
-            // Processing Question Header
+            // Processing Question Header.
 
-            if (preg_match("~^:TYPE:MC:1(.*)~i",$line,$webct_options)) {
-                // Multiple Choice Question with only one good answer
+            if (preg_match("~^:TYPE:MC:1(.*)~i", $line, $webctoptions)) {
+                // Multiple Choice Question with only one good answer.
                 $question = $this->defaultquestion();
                 $question->feedback = array();
                 $question->qtype = 'multichoice';
-                $question->single = 1;        // Only one answer is allowed
-                $ignore_rest_of_question = FALSE;
+                $question->single = 1;        // Only one answer is allowed.
+                $ignorerestofquestion = false;
                 continue;
             }
 
-            if (preg_match("~^:TYPE:MC:N(.*)~i",$line,$webct_options)) {
-                // Multiple Choice Question with several good answers
+            if (preg_match("~^:TYPE:MC:N(.*)~i", $line, $webctoptions)) {
+                // Multiple Choice Question with several good answers.
                 $question = $this->defaultquestion();
                 $question->feedback = array();
                 $question->qtype = 'multichoice';
-                $question->single = 0;        // Many answers allowed
-                $ignore_rest_of_question = FALSE;
+                $question->single = 0;        // Many answers allowed.
+                $ignorerestofquestion = false;
                 continue;
             }
 
-            if (preg_match("~^:TYPE:S~i",$line)) {
-                // Short Answer Question
+            if (preg_match("~^:TYPE:S~i", $line)) {
+                // Short Answer Question.
                 $question = $this->defaultquestion();
                 $question->feedback = array();
                 $question->qtype = 'shortanswer';
-                $question->usecase = 0;       // Ignore case
-                $ignore_rest_of_question = FALSE;
+                $question->usecase = 0;       // Ignore case.
+                $ignorerestofquestion = false;
                 continue;
             }
 
-            if (preg_match("~^:TYPE:C~i",$line)) {
-                // Calculated Question
+            if (preg_match("~^:TYPE:C~i", $line)) {
+                // Calculated Question.
                 $question = $this->defaultquestion();
                 $question->qtype = 'calculated';
-                $question->answers = array(); // No problem as they go as :FORMULA: from webct
+                $question->answers = array(); // No problem as they go as :FORMULA: from webct.
                 $question->units = array();
                 $question->dataset = array();
 
-                // To make us pass the end-of-question sanity checks
+                // To make us pass the end-of-question sanity checks.
                 $question->answer = array('dummy');
                 $question->fraction = array('1.0');
                 $question->feedback = array();
 
                 $currentchoice = -1;
-                $ignore_rest_of_question = FALSE;
+                $ignorerestofquestion = false;
                 continue;
             }
 
-            if (preg_match("~^:TYPE:M~i",$line)) {
-                // Match Question
+            if (preg_match("~^:TYPE:M~i", $line)) {
+                // Match Question.
                 $question = $this->defaultquestion();
                 $question->qtype = 'match';
                 $question->feedback = array();
-                $ignore_rest_of_question = FALSE;         // match question processing is not debugged
+                $ignorerestofquestion = false;         // Match question processing is not debugged.
                 continue;
             }
 
-            if (preg_match("~^:TYPE:P~i",$line)) {
-                // Paragraph Question
-                $warnings[] = get_string("paragraphquestion", "qformat_webct", $nLineCounter);
+            if (preg_match("~^:TYPE:P~i", $line)) {
+                // 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->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;
+            }
+
+            if (preg_match("~^:TYPE:~i", $line)) {
+                // Unknow question type.
+                $warnings[] = get_string('unknowntype', 'qformat_webct', $nlinecounter);
                 unset($question);
-                $ignore_rest_of_question = TRUE;         // Question Type not handled by Moodle
+                $ignorerestofquestion = true;         // Question Type not handled by Moodle.
                 continue;
             }
 
-            if (preg_match("~^:TYPE:~i",$line)) {
-                // Unknow Question
-                $warnings[] = get_string("unknowntype", "qformat_webct", $nLineCounter);
-                unset($question);
-                $ignore_rest_of_question = TRUE;         // Question Type not handled by Moodle
+            if ($ignorerestofquestion) {
                 continue;
             }
 
-            if ($ignore_rest_of_question) {
-                continue;
-            }
-
-            if (preg_match("~^:TITLE:(.*)~i",$line,$webct_options)) {
-                $name = trim($webct_options[1]);
+            if (preg_match("~^:TITLE:(.*)~i", $line, $webctoptions)) {
+                $name = trim($webctoptions[1]);
                 $question->name = $this->clean_question_name($name);
                 continue;
             }
 
-            if (preg_match("~^:IMAGE:(.*)~i",$line,$webct_options)) {
-                $filename = trim($webct_options[1]);
-                if (preg_match("~^http://~i",$filename)) {
+            if (preg_match("~^:IMAGE:(.*)~i", $line, $webctoptions)) {
+                $filename = trim($webctoptions[1]);
+                if (preg_match("~^http://~i", $filename)) {
                     $question->image = $filename;
                 }
                 continue;
             }
 
             // Need to put the parsing of calculated items here to avoid ambitiuosness:
-            // if question isn't defined yet there is nothing to do here (avoid notices)
+            // if question isn't defined yet there is nothing to do here (avoid notices).
             if (!isset($question)) {
                 continue;
             }
             if (isset($question->qtype ) && 'calculated' == $question->qtype && preg_match(
-                    "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?($webctnumberregex)~", $line, $webct_options)) {
-                $datasetname = preg_replace('/^::/', '', $webct_options[1]);
-                $datasetvalue = qformat_webct_convert_formula($webct_options[4]);
-                switch ($webct_options[2]) {
+                    "~^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?($webctnumberregex)~", $line, $webctoptions)) {
+                $datasetname = preg_replace('/^::/', '', $webctoptions[1]);
+                $datasetvalue = qformat_webct_convert_formula($webctoptions[4]);
+                switch ($webctoptions[2]) {
                     case 'MIN':
                         $question->dataset[$datasetname]->min = $datasetvalue;
                         break;
@@ -492,123 +695,130 @@ class qformat_webct extends qformat_default {
                         $question->dataset[$datasetname]->max = $datasetvalue;
                         break;
                     case 'DEC':
-                        $datasetvalue = floor($datasetvalue); // int only!
+                        $datasetvalue = floor($datasetvalue); // Int only!
                         $question->dataset[$datasetname]->length = max(0, $datasetvalue);
                         break;
                     default:
-                        // The VAL case:
-                        $question->dataset[$datasetname]->datasetitem[$webct_options[3]] = new stdClass();
-                        $question->dataset[$datasetname]->datasetitem[$webct_options[3]]->itemnumber = $webct_options[3];
-                        $question->dataset[$datasetname]->datasetitem[$webct_options[3]]->value  = $datasetvalue;
+                        // The VAL case.
+                        $question->dataset[$datasetname]->datasetitem[$webctoptions[3]] = new stdClass();
+                        $question->dataset[$datasetname]->datasetitem[$webctoptions[3]]->itemnumber = $webctoptions[3];
+                        $question->dataset[$datasetname]->datasetitem[$webctoptions[3]]->value  = $datasetvalue;
                         break;
                 }
                 continue;
             }
 
-
-            $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
+            $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.
                 continue;
             }
 
-            if (preg_match("~^:ANSWER([0-9]+):([^:]+):([0-9\.\-]+):(.*)~i",$line,$webct_options)) { // Shortanswer.
-                $currentchoice=$webct_options[1];
-                $answertext=$webct_options[2];            // Start gathering next lines
-                $question->fraction[$currentchoice]=($webct_options[3]/100);
+            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);
                 continue;
             }
 
-            if (preg_match("~^:ANSWER([0-9]+):([0-9\.\-]+)~i",$line,$webct_options)) {
-                $answertext="";                 // Start gathering next lines
-                $currentchoice=$webct_options[1];
-                $question->fraction[$currentchoice]=($webct_options[2]/100);
+            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);
                 continue;
             }
 
-            if (preg_match('~^:FORMULA:(.*)~i', $line, $webct_options)) {
-                // Answer for a calculated question
+            if (preg_match('~^:ANSWER:~i', $line)) { // Essay.
+                $graderinfo  = '';      // Start gathering next lines.
+                continue;
+            }
+
+            if (preg_match('~^:FORMULA:(.*)~i', $line, $webctoptions)) {
+                // Answer for a calculated question.
                 ++$currentchoice;
                 $question->answers[$currentchoice] =
-                        qformat_webct_convert_formula($webct_options[1]);
+                        qformat_webct_convert_formula($webctoptions[1]);
 
-                // Default settings:
+                // Default settings.
                 $question->fraction[$currentchoice] = 1.0;
                 $question->tolerance[$currentchoice] = 0.0;
-                $question->tolerancetype[$currentchoice] = 2; // nominal (units in webct)
-                $question->feedback[$currentchoice] = '';
+                $question->tolerancetype[$currentchoice] = 2; // Nominal (units in webct).
+                $question->feedback[$currentchoice]['text'] = '';
+                $question->feedback[$currentchoice]['format'] = FORMAT_HTML;
                 $question->correctanswerlength[$currentchoice] = 4;
 
-                $datasetnames = question_bank::get_qtype('calculated')->
-                        find_dataset_names($webct_options[1]);
+                $datasetnames =
+                        question_bank::get_qtype('calculated')->find_dataset_names($webctoptions[1]);
                 foreach ($datasetnames as $datasetname) {
                     $question->dataset[$datasetname] = new stdClass();
                     $question->dataset[$datasetname]->datasetitem = array();
-                    $question->dataset[$datasetname]->name = $datasetname ;
+                    $question->dataset[$datasetname]->name = $datasetname;
                     $question->dataset[$datasetname]->distribution = 'uniform';
-                    $question->dataset[$datasetname]->status ='private';
+                    $question->dataset[$datasetname]->status = 'private';
                 }
                 continue;
             }
 
-            if (preg_match("~^:L([0-9]+)~i",$line,$webct_options)) {
-                $answertext="";                 // Start gathering next lines
-                $currentchoice=$webct_options[1];
-                $question->fraction[$currentchoice]=1;
+            if (preg_match("~^:L([0-9]+)~i", $line, $webctoptions)) {
+                $answertext = '';                 // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
+                $question->fraction[$currentchoice] = 1;
                 continue;
             }
 
-            if (preg_match("~^:R([0-9]+)~i",$line,$webct_options)) {
-                $responsetext="";                // Start gathering next lines
-                $currentchoice=$webct_options[1];
+            if (preg_match("~^:R([0-9]+)~i", $line, $webctoptions)) {
+                $responsetext = '';                // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
 
-            if (preg_match("~^:REASON([0-9]+):?~i",$line,$webct_options)) {
-                $feedbacktext="";               // Start gathering next lines
-                $currentchoice=$webct_options[1];
+            if (preg_match("~^:REASON([0-9]+):?~i", $line, $webctoptions)) {
+                $feedbacktext = '';               // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
-            if (preg_match("~^:FEEDBACK([0-9]+):?~i",$line,$webct_options)) {
-                $generalfeedbacktext="";               // Start gathering next lines
-                $currentchoice=$webct_options[1];
+            if (preg_match("~^:FEEDBACK([0-9]+):?~i", $line, $webctoptions)) {
+                $generalfeedbacktext = '';               // Start gathering next lines.
+                $currentchoice = $webctoptions[1];
                 continue;
             }
-            if (preg_match('~^:FEEDBACK:(.*)~i',$line,$webct_options)) {
-                $generalfeedbacktext="";               // Start gathering next lines
+            if (preg_match('~^:FEEDBACK:(.*)~i', $line, $webctoptions)) {
+                $generalfeedbacktext = '';               // Start gathering next lines.
                 continue;
             }
-            if (preg_match('~^:LAYOUT:(.*)~i',$line,$webct_options)) {
-            //    ignore  since layout in question_multichoice  is no more used in moodle
-            //    $webct_options[1] contains either vertical or horizontal ;
+            if (preg_match('~^:LAYOUT:(.*)~i', $line, $webctoptions)) {
+                // Ignore  since layout in question_multichoice  is no more used in Moodle.
+                // $webctoptions[1] contains either vertical or horizontal.
                 continue;
             }
 
-            if (isset($question->qtype ) && 'calculated' == $question->qtype && preg_match('~^:ANS-DEC:([1-9][0-9]*)~i', $line, $webct_options)) {
-                // We can but hope that this always appear before the ANSTYPE property
-                $question->correctanswerlength[$currentchoice] = $webct_options[1];
+            if (isset($question->qtype ) && 'calculated' == $question->qtype
+                    && preg_match('~^:ANS-DEC:([1-9][0-9]*)~i', $line, $webctoptions)) {
+                // We can but hope that this always appear before the ANSTYPE property.
+                $question->correctanswerlength[$currentchoice] = $webctoptions[1];
                 continue;
             }
 
-            if (isset($question->qtype )&& 'calculated' == $question->qtype && preg_match("~^:TOL:($webctnumberregex)~i", $line, $webct_options)) {
-                // We can but hope that this always appear before the TOL property
+            if (isset($question->qtype )&& 'calculated' == $question->qtype
+                    && preg_match("~^:TOL:($webctnumberregex)~i", $line, $webctoptions)) {
+                // We can but hope that this always appear before the TOL property.
                 $question->tolerance[$currentchoice] =
-                        qformat_webct_convert_formula($webct_options[1]);
+                        qformat_webct_convert_formula($webctoptions[1]);
                 continue;
             }
 
             if (isset($question->qtype )&& 'calculated' == $question->qtype && preg_match('~^:TOLTYPE:percent~i', $line)) {
-                // Percentage case is handled as relative in Moodle:
+                // Percentage case is handled as relative in Moodle.
                 $question->tolerance[$currentchoice]  /= 100;
-                $question->tolerancetype[$currentchoice] = 1; // Relative
+                $question->tolerancetype[$currentchoice] = 1; // Relative.
                 continue;
             }
 
-            if (preg_match('~^:UNITS:(.+)~i', $line, $webct_options)
-                    and $webctunits = trim($webct_options[1])) {
+            if (preg_match('~^:UNITS:(.+)~i', $line, $webctoptions)
+                    and $webctunits = trim($webctoptions[1])) {
                 // This is a guess - I really do not know how different webct units are separated...
                 $webctunits = explode(':', $webctunits);
-                $unitrec->multiplier = 1.0; // Webct does not seem to support this
+                $unitrec->multiplier = 1.0; // Webct does not seem to support this.
                 foreach ($webctunits as $webctunit) {
                     $unitrec->unit = trim($webctunit);
                     $question->units[] = $unitrec;
@@ -616,10 +826,10 @@ class qformat_webct extends qformat_default {
                 continue;
             }
 
-            if (!empty($question->units) && preg_match('~^:UNITREQ:(.*)~i', $line, $webct_options)
-                    && !$webct_options[1]) {
-                // There are units but units are not required so add the no unit alternative
-                // We can but hope that the UNITS property always appear before this property
+            if (!empty($question->units) && preg_match('~^:UNITREQ:(.*)~i', $line, $webctoptions)
+                    && !$webctoptions[1]) {
+                // There are units but units are not required so add the no unit alternative.
+                // We can but hope that the UNITS property always appear before this property.
                 $unitrec->unit = '';
                 $unitrec->multiplier = 1.0;
                 $question->units[] = $unitrec;
@@ -628,38 +838,36 @@ class qformat_webct extends qformat_default {
 
             if (!empty($question->units) && preg_match('~^:UNITCASE:~i', $line)) {
                 // This could be important but I was not able to figure out how
-                // it works so I ignore it for now
+                // it works so I ignore it for now.
                 continue;
             }
 
             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;
             }
         }
 
-        if (sizeof($errors) > 0) {
-            echo "

".get_string("errorsdetected", "qformat_webct", sizeof($errors))."

    "; - foreach($errors as $error) { + if (count($errors) > 0) { + echo '

    '.get_string('errorsdetected', 'qformat_webct', count($errors)).'

      '; + foreach ($errors as $error) { echo "
    • $error
    • "; } - echo "
    "; - unset($questions); // no questions imported + echo '
'; + unset($questions); // No questions imported. } - if (sizeof($warnings) > 0) { - echo "

".get_string("warningsdetected", "qformat_webct", sizeof($warnings))."

    "; - foreach($warnings as $warning) { + if (count($warnings) > 0) { + echo '

    '.get_string('warningsdetected', 'qformat_webct', count($warnings)).'

      '; + foreach ($warnings as $warning) { echo "
    • $warning
    • "; } - echo "
    "; + echo '
'; } return $questions; } } - -?> diff --git a/question/format/webct/lang/en/qformat_webct.php b/question/format/webct/lang/en/qformat_webct.php index 43a3b732279..21e7093a65e 100644 --- a/question/format/webct/lang/en/qformat_webct.php +++ b/question/format/webct/lang/en/qformat_webct.php @@ -17,8 +17,7 @@ /** * Strings for component 'qformat_webct', language 'en', branch 'MOODLE_20_STABLE' * - * @package qformat - * @subpackage webct + * @package qformat_webct * @copyright 2010 Helen Foster * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -31,6 +30,6 @@ $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 index 00000000000..e94efe97be4 --- /dev/null +++ b/question/format/webct/tests/fixtures/sample_webct.txt @@ -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 Ultimate Answer. +:ANSWER2:100:H +False +:REASON2:H +42 is the Ultimate 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 +What's between orange and green in the spectrum? +: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 +What's between orange and green in the spectrum? +: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 +Classify the animals. +: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: __________ +: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 index 00000000000..12783aa6f92 --- /dev/null +++ b/question/format/webct/tests/webctformat_test.php @@ -0,0 +1,366 @@ +. + +/** + * 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 = 'Classify the animals.'; + $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 = 'What\'s between orange and green in the spectrum?'; + $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 = 'What\'s between orange and green in the spectrum?'; + $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 Ultimate Answer.', + 'format' => FORMAT_HTML, + ), + 2 => array( + 'text' => '42 is the Ultimate 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: __________'; + $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); + } +} diff --git a/question/format/webct/version.php b/question/format/webct/version.php index c0dc0b5ca81..a8c2ed3a5da 100644 --- a/question/format/webct/version.php +++ b/question/format/webct/version.php @@ -15,10 +15,9 @@ // along with Moodle. If not, see . /** - * Version information for the calculated question type. + * Version information for the Web CT question import format. * - * @package qformat - * @subpackage webct + * @package qformat_webct * @copyright 2011 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -26,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qformat_webct'; -$plugin->version = 2012112900; +$plugin->version = 2012112901; $plugin->requires = 2012112900;