Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

672 lines (599 sloc) 30.318 kb
<?php // $Id$
///////////////////////////////////////////////////////////////////////////
// //
// WebCT FORMAT //
// //
///////////////////////////////////////////////////////////////////////////
// //
// NOTICE OF COPYRIGHT //
// //
// Part of Moodle - Modular Object-Oriented Dynamic Learning Environment //
// http://moodle.com //
// //
// Copyright (C) 2004 ASP Consulting http://www.asp-consulting.net //
// //
// This program 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 2 of the License, or //
// (at your option) any later version. //
// //
// This program 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: //
// //
// http://www.gnu.org/copyleft/gpl.html //
// //
///////////////////////////////////////////////////////////////////////////
// Based on format.php, included by ../../import.php
/**
* @package questionbank
* @subpackage importexport
*/
function unhtmlentities($string){
$search = array ("'<script[?>]*?>.*?</script>'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",
"'&(nbsp|#160);'i",
"'&(iexcl|#161);'i",
"'&(cent|#162);'i",
"'&(pound|#163);'i",
"'&(copy|#169);'i",
"'&#(\d+);'e"); // Evaluate like PHP
$replace = array ("",
"",
"\\1",
"\"",
"&",
"<",
"?>",
" ",
chr(161),
chr(162),
chr(163),
chr(169),
"chr(\\1)");
return preg_replace ($search, $replace, $string);
}
function qformat_webct_convert_formula($formula) {
// Remove empty space, as it would cause problems otherwise:
$formula = str_replace(' ', '', $formula);
// Remove paranthesis after e,E and *10**:
while (ereg('[0-9.](e|E|\\*10\\*\\*)\\([+-]?[0-9]+\\)', $formula, $regs)) {
$formula = str_replace(
$regs[0], ereg_replace('[)(]', '', $regs[0]), $formula);
}
// Replace *10** with e where possible
while (ereg(
'(^[+-]?|[^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
while (ereg('(^|[^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)
while (2 == count($splits = explode('**', $formula, 2))) {
// Find $base
if (ereg('^(.*[^0-9.eE])?(([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][+-]?[0-9]+)?|\\{[^}]*\\})$',
$splits[0], $regs)) {
// The simple cases
$base = $regs[2];
$splits[0] = $regs[1];
} else if (ereg('\\)$', $splits[0])) {
// Find the start of this parenthesis
$deep = 1;
for ($i = 1 ; $deep ; ++$i) {
if (!ereg('^(.*[^[:alnum:]_])?([[:alnum:]_]*([)(])([^)(]*[)(]){'.$i.'})$',
$splits[0], $regs)) {
error("Parenthesis before ** is not properly started in $splits[0]**");
}
if ('(' == $regs[3]) {
--$deep;
} else if (')' == $regs[3]) {
++$deep;
} else {
error("Impossible character $regs[3] detected as parenthesis character");
}
}
$base = $regs[2];
$splits[0] = $regs[1];
} else {
error("Bad base before **: $splits[0]**");
}
// Find $exp (similar to above but a little easier)
if (ereg('^([+-]?(\\{[^}]\\}|([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][+-]?[0-9]+)?))(.*)',
$splits[1], $regs)) {
// The simple case
$exp = $regs[1];
$splits[1] = $regs[6];
} else if (ereg('^[+-]?[[:alnum:]_]*\\(', $splits[1])) {
// Find the end of the parenthesis
$deep = 1;
for ($i = 1 ; $deep ; ++$i) {
if (!ereg('^([+-]?[[:alnum:]_]*([)(][^)(]*){'.$i.'}([)(]))(.*)',
$splits[1], $regs)) {
error("Parenthesis after ** is not properly closed in **$splits[1]");
}
if (')' == $regs[3]) {
--$deep;
} else if ('(' == $regs[3]) {
++$deep;
} else {
error("Impossible character $regs[3] detected as parenthesis character");
}
}
$exp = $regs[1];
$splits[1] = $regs[4];
}
// Replace it!
$formula = "$splits[0]pow($base,$exp)$splits[1]";
}
// Nothing more is known to need to be converted
return $formula;
}
class qformat_webct extends qformat_default {
function provide_import() {
return true;
}
function readquestions ($lines) {
global $QTYPES ;
// $qtypecalculated = new qformat_webct_modified_calculated_qtype();
$webctnumberregex =
'[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)((e|E|\\*10\\*\\*)([+-]?[0-9]+|\\([+-]?[0-9]+\\)))?';
$questions = array();
$errors = array();
$warnings = array();
$webct_options = array();
$ignore_rest_of_question = FALSE;
$nLineCounter = 0;
$nQuestionStartLine = 0;
$bIsHTMLText = FALSE;
$lines[] = ":EOF:"; // for an easiest processing of the last line
// $question = $this->defaultquestion();
foreach ($lines as $line) {
$nLineCounter++;
$line = iconv("Windows-1252","UTF-8",$line);
// Processing multiples lines strings
if (isset($questiontext) and is_string($questiontext)) {
if (ereg("^:",$line)) {
$question->questiontext = addslashes(trim($questiontext));
unset($questiontext);
}
else {
$questiontext .= str_replace('\:', ':', $line);
continue;
}
}
if (isset($answertext) and is_string($answertext)) {
if (ereg("^:",$line)) {
$answertext = addslashes(trim($answertext));
$question->answer[$currentchoice] = $answertext;
$question->subanswers[$currentchoice] = $answertext;
unset($answertext);
}
else {
$answertext .= str_replace('\:', ':', $line);
continue;
}
}
if (isset($responsetext) and is_string($responsetext)) {
if (ereg("^:",$line)) {
$question->subquestions[$currentchoice] = addslashes(trim($responsetext));
unset($responsetext);
}
else {
$responsetext .= str_replace('\:', ':', $line);
continue;
}
}
if (isset($feedbacktext) and is_string($feedbacktext)) {
if (ereg("^:",$line)) {
$question->feedback[$currentchoice] = addslashes(trim($feedbacktext));
unset($feedbacktext);
}
else {
$feedbacktext .= str_replace('\:', ':', $line);
continue;
}
}
if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) {
if (ereg("^:",$line)) {
$question->tempgeneralfeedback= addslashes(trim($generalfeedbacktext));
unset($generalfeedbacktext);
}
else {
$generalfeedbacktext .= str_replace('\:', ':', $line);
continue;
}
}
$line = trim($line);
if (eregi("^:(TYPE|EOF):",$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
if (!isset($question->name)) {
$question->name = $question->questiontext;
}
if (strlen($question->name) > 255) {
$question->name = substr($question->name,0,250)."...";
$warnings[] = get_string("questionnametoolong", "quiz", $nQuestionStartLine);
}
if (!isset($question->defaultgrade)) {
$question->defaultgrade = 1;
}
if (!isset($question->image)) {
$question->image = "";
}
// Perform sanity checks
$QuestionOK = TRUE;
if (strlen($question->questiontext) == 0) {
$warnings[] = get_string("missingquestion", "quiz", $nQuestionStartLine);
$QuestionOK = FALSE;
}
if (sizeof($question->answer) < 1) { // a question must have at least 1 answer
$errors[] = get_string("missinganswer", "quiz", $nQuestionStartLine);
$QuestionOK = FALSE;
}
else {
// Create empty feedback array
foreach ($question->answer as $key => $dataanswer) {
if(!isset( $question->feedback[$key])){
$question->feedback[$key] = '';
}
}
// 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;
} else {
foreach ($question->answer as $key => $dataanswer) {
if ($question->tempgeneralfeedback !=''){
$question->feedback[$key] = $question->tempgeneralfeedback.'<br/>'.$question->feedback[$key];
}
}
}
unset($question->tempgeneralfeedback);
}
$maxfraction = -1;
$totalfraction = 0;
foreach($question->fraction as $fraction) {
if ($fraction > 0) {
$totalfraction += $fraction;
}
if ($fraction > $maxfraction) {
$maxfraction = $fraction;
}
}
switch ($question->qtype) {
case SHORTANSWER:
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$errors[] = "'$question->name': ".get_string("wronggrade", "quiz", $nLineCounter).' '.get_string("fractionsnomax", "quiz", $maxfraction);
$QuestionOK = FALSE;
}
break;
case MULTICHOICE:
if ($question->single) {
if ($maxfraction != 1) {
$maxfraction = $maxfraction * 100;
$errors[] = "'$question->name': ".get_string("wronggrade", "quiz", $nLineCounter).' '.get_string("fractionsnomax", "quiz", $maxfraction);
$QuestionOK = FALSE;
}
} else {
$totalfraction = round($totalfraction,2);
if ($totalfraction != 1) {
$totalfraction = $totalfraction * 100;
$errors[] = "'$question->name': ".get_string("wronggrade", "quiz", $nLineCounter).' '.get_string("fractionsaddwrong", "quiz", $totalfraction);
$QuestionOK = FALSE;
}
}
break;
case CALCULATED:
foreach ($question->answers as $answer) {
if ($formulaerror =qtype_calculated_find_formula_errors($answer)) { //$QTYPES['calculated']->
$warnings[] = "'$question->name': ". $formulaerror;
$QuestionOK = FALSE;
}
}
foreach ($question->dataset as $dataset) {
$dataset->itemcount=count($dataset->datasetitem);
}
$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) {
$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 ;
$question->answer[] = 'dummy';
$question->subanswers[] = 'dummy';
$question->subquestions[] = 'dummy';
$question->fraction[] = '0.0';
$question->feedback[] = '';
}
break;
default:
// No problemo
}
}
if ($QuestionOK) {
// echo "<pre>"; print_r ($question);
$questions[] = $question; // store it
unset($question); // and prepare a new one
$question = $this->defaultquestion();
}
}
$nQuestionStartLine = $nLineCounter;
}
// Processing Question Header
if (eregi("^:TYPE:MC:1(.*)",$line,$webct_options)) {
// 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;
continue;
}
if (eregi("^:TYPE:MC:N(.*)",$line,$webct_options)) {
// 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;
continue;
}
if (eregi("^:TYPE:S",$line)) {
// Short Answer Question
$question = $this->defaultquestion();
$question->feedback = array();
$question->qtype = SHORTANSWER;
$question->usecase = 0; // Ignore case
$ignore_rest_of_question = FALSE;
continue;
}
if (eregi("^:TYPE:C",$line)) {
// Calculated Question
/* $warnings[] = get_string("calculatedquestion", "quiz", $nLineCounter);
unset($question);
$ignore_rest_of_question = TRUE; // Question Type not handled by Moodle
*/
$question = $this->defaultquestion();
$question->qtype = CALCULATED;
$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
$question->answer = array('dummy');
$question->fraction = array('1.0');
$question->feedback = array();
$currentchoice = -1;
$ignore_rest_of_question = FALSE;
continue;
}
if (eregi("^:TYPE:M",$line)) {
// Match Question
$question = $this->defaultquestion();
$question->qtype = MATCH;
$question->feedback = array();
$ignore_rest_of_question = FALSE; // match question processing is not debugged
continue;
}
if (eregi("^:TYPE:P",$line)) {
// Paragraph Question
$warnings[] = get_string("paragraphquestion", "quiz", $nLineCounter);
unset($question);
$ignore_rest_of_question = TRUE; // Question Type not handled by Moodle
continue;
}
if (eregi("^:TYPE:",$line)) {
// Unknow Question
$warnings[] = get_string("unknowntype", "quiz", $nLineCounter);
unset($question);
$ignore_rest_of_question = TRUE; // Question Type not handled by Moodle
continue;
}
if ($ignore_rest_of_question) {
continue;
}
if (eregi("^:TITLE:(.*)",$line,$webct_options)) {
$name = trim($webct_options[1]);
if (strlen($name) > 255) {
$name = substr($name,0,250)."...";
$warnings[] = get_string("questionnametoolong", "quiz", $nLineCounter);
}
$question->name = addslashes($name);
continue;
}
if (eregi("^:IMAGE:(.*)",$line,$webct_options)) {
$filename = trim($webct_options[1]);
if (eregi("^http://",$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 (!isset($question)) {
continue;
}
if (isset($question->qtype ) && CALCULATED == $question->qtype && ereg(
"^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?($webctnumberregex)", $line, $webct_options)) {
$datasetname = ereg_replace('^::', '', $webct_options[1]);
$datasetvalue = qformat_webct_convert_formula($webct_options[4]);
switch ($webct_options[2]) {
case 'MIN':
$question->dataset[$datasetname]->min = $datasetvalue;
break;
case 'MAX':
$question->dataset[$datasetname]->max = $datasetvalue;
break;
case 'DEC':
$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;
break;
}
continue;
}
$bIsHTMLText = eregi(":H$",$line); // True if next lines are coded in HTML
if (eregi("^:QUESTION",$line)) {
$questiontext=""; // Start gathering next lines
continue;
}
if (eregi("^:ANSWER([0-9]+):([^:]+):([0-9\.\-]+):(.*)",$line,$webct_options)) { /// SHORTANSWER
$currentchoice=$webct_options[1];
$answertext=$webct_options[2]; // Start gathering next lines
$question->fraction[$currentchoice]=($webct_options[3]/100);
continue;
}
if (eregi("^:ANSWER([0-9]+):([0-9\.\-]+)",$line,$webct_options)) {
$answertext=""; // Start gathering next lines
$currentchoice=$webct_options[1];
$question->fraction[$currentchoice]=($webct_options[2]/100);
continue;
}
if (eregi('^:FORMULA:(.*)', $line, $webct_options)) {
// Answer for a CALCULATED question
++$currentchoice;
$question->answers[$currentchoice] =
qformat_webct_convert_formula($webct_options[1]);
// Default settings:
$question->fraction[$currentchoice] = 1.0;
$question->tolerance[$currentchoice] = 0.0;
$question->tolerancetype[$currentchoice] = 2; // nominal (units in webct)
$question->feedback[$currentchoice] = '';
$question->correctanswerlength[$currentchoice] = 4;
$datasetnames = $QTYPES[CALCULATED]->find_dataset_names($webct_options[1]);
foreach ($datasetnames as $datasetname) {
$question->dataset[$datasetname] = new stdClass();
$question->dataset[$datasetname]->datasetitem = array();
$question->dataset[$datasetname]->name = $datasetname ;
$question->dataset[$datasetname]->distribution = 'uniform';
$question->dataset[$datasetname]->status ='private';
}
continue;
}
if (eregi("^:L([0-9]+)",$line,$webct_options)) {
$answertext=""; // Start gathering next lines
$currentchoice=$webct_options[1];
$question->fraction[$currentchoice]=1;
continue;
}
if (eregi("^:R([0-9]+)",$line,$webct_options)) {
$responsetext=""; // Start gathering next lines
$currentchoice=$webct_options[1];
continue;
}
if (eregi("^:REASON([0-9]+):?",$line,$webct_options)) {
$feedbacktext=""; // Start gathering next lines
$currentchoice=$webct_options[1];
continue;
}
if (eregi("^:FEEDBACK([0-9]+):?",$line,$webct_options)) {
$generalfeedbacktext=""; // Start gathering next lines
$currentchoice=$webct_options[1];
continue;
}
if (eregi('^:FEEDBACK:(.*)',$line,$webct_options)) {
$generalfeedbacktext=""; // Start gathering next lines
continue;
}
if (eregi('^:LAYOUT:(.*)',$line,$webct_options)) {
// ignore since layout in question_multichoice is no more used in moodle
// $webct_options[1] contains either vertical or horizontal ;
continue;
}
if (isset($question->qtype ) && CALCULATED == $question->qtype && eregi('^:ANS-DEC:([1-9][0-9]*)', $line, $webct_options)) {
// We can but hope that this always appear before the ANSTYPE property
$question->correctanswerlength[$currentchoice] = $webct_options[1];
continue;
}
if (isset($question->qtype )&& CALCULATED == $question->qtype && eregi("^:TOL:($webctnumberregex)", $line, $webct_options)) {
// We can but hope that this always appear before the TOL property
$question->tolerance[$currentchoice] =
qformat_webct_convert_formula($webct_options[1]);
continue;
}
if (isset($question->qtype )&& CALCULATED == $question->qtype && eregi('^:TOLTYPE:percent', $line)) {
// Percentage case is handled as relative in Moodle:
$question->tolerance[$currentchoice] /= 100;
$question->tolerancetype[$currentchoice] = 1; // Relative
continue;
}
if (eregi('^:UNITS:(.+)', $line, $webct_options)
and $webctunits = trim($webct_options[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
foreach ($webctunits as $webctunit) {
$unitrec->unit = trim($webctunit);
$question->units[] = $unitrec;
}
continue;
}
if (!empty($question->units) && eregi('^:UNITREQ:(.*)', $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
$unitrec->unit = '';
$unitrec->multiplier = 1.0;
$question->units[] = $unitrec;
continue;
}
if (!empty($question->units) && eregi('^:UNITCASE:', $line)) {
// This could be important but I was not able to figure out how
// it works so I ignore it for now
continue;
}
if (isset($question->qtype )&& CALCULATED == $question->qtype && eregi('^:ANSTYPE:dec', $line)) {
$question->correctanswerformat[$currentchoice]='1';
continue;
}
if (isset($question->qtype )&& CALCULATED == $question->qtype && eregi('^:ANSTYPE:sig', $line)) {
$question->correctanswerformat[$currentchoice]='2';
continue;
}
}
if (sizeof($errors) > 0) {
echo "<p>".get_string("errorsdetected", "quiz", sizeof($errors))."</p><ul>";
foreach($errors as $error) {
echo "<li>$error</li>";
}
echo "</ul>";
unset($questions); // no questions imported
}
if (sizeof($warnings) > 0) {
echo "<p>".get_string("warningsdetected", "quiz", sizeof($warnings))."</p><ul>";
foreach($warnings as $warning) {
echo "<li>$warning</li>";
}
echo "</ul>";
}
return $questions;
}
}
?>
Jump to Line
Something went wrong with that request. Please try again.