public function readquestions($lines) { $webctnumberregex = '[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)((e|E|\\*10\\*\\*)([+-]?[0-9]+|\\([+-]?[0-9]+\\)))?'; $questions = array(); $errors = array(); $warnings = array(); $webctoptions = array(); $ignorerestofquestion = false; $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 = textlib::convert($line, 'windows-1252', 'utf-8'); // Processing multiples lines strings. if (isset($questiontext) and is_string($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 { $questiontext .= str_replace('\\:', ':', $line); continue; } } if (isset($answertext) and is_string($answertext)) { if (preg_match("~^:~", $line)) { $answertext = trim($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); continue; } } if (isset($responsetext) and is_string($responsetext)) { if (preg_match("~^:~", $line)) { $question->subquestions[$currentchoice] = trim($responsetext); unset($responsetext); } else { $responsetext .= str_replace('\\:', ':', $line); continue; } } if (isset($feedbacktext) and is_string($feedbacktext)) { if (preg_match("~^:~", $line)) { $question->feedback[$currentchoice] = $this->text_field(trim($feedbacktext)); unset($feedbacktext); } else { $feedbacktext .= str_replace('\\:', ':', $line); continue; } } if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) { if (preg_match("~^:~", $line)) { $question->tempgeneralfeedback = trim($generalfeedbacktext); unset($generalfeedbacktext); } 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. // Setup default value of missing fields. if (!isset($question->name)) { $question->name = $this->create_default_question_name($question->questiontext, get_string('questionname', 'question')); } if (!isset($question->defaultmark)) { $question->defaultmark = 1; } if (!isset($question->image)) { $question->image = ''; } // Perform sanity checks. $questionok = true; if (strlen($question->questiontext) == 0) { $warnings[] = get_string('missingquestion', 'qformat_webct', $nquestionstartline); $questionok = false; } 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]['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)) { $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]['text'] = $question->tempgeneralfeedback . '<br/>' . $question->feedback[$key]['text']; } } } 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', '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; } } else { $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; } } break; case 'calculated': foreach ($question->answers as $answer) { if ($formulaerror = qtype_calculated_find_formula_errors($answer)) { $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. $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; $question->answer[] = 'dummy'; $question->subanswers[] = 'dummy'; $question->subquestions[] = 'dummy'; $question->fraction[] = '0.0'; $question->feedback[] = ''; } break; default: // No problemo. } } if ($questionok) { $questions[] = $question; // Store it. unset($question); // And prepare a new one. $question = $this->defaultquestion(); } } $nquestionstartline = $nlinecounter; } // Processing Question Header. 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. $ignorerestofquestion = false; continue; } 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. $ignorerestofquestion = false; continue; } if (preg_match("~^:TYPE:S~i", $line)) { // Short Answer Question. $question = $this->defaultquestion(); $question->feedback = array(); $question->qtype = 'shortanswer'; $question->usecase = 0; // Ignore case. $ignorerestofquestion = false; continue; } if (preg_match("~^:TYPE:C~i", $line)) { // Calculated Question. $question = $this->defaultquestion(); $question->qtype = 'calculated'; $question->answer = array(); // No problem as they go as :FORMULA: from webct. $question->units = array(); $question->dataset = array(); $question->fraction = array('1.0'); $question->feedback = array(); $currentchoice = -1; $ignorerestofquestion = false; continue; } if (preg_match("~^:TYPE:M~i", $line)) { // Match Question. $question = $this->defaultquestion(); $question->qtype = 'match'; $question->feedback = array(); $ignorerestofquestion = false; // Match question processing is not debugged. continue; } 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->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; } if (preg_match("~^:TYPE:~i", $line)) { // Unknow question type. $warnings[] = get_string('unknowntype', 'qformat_webct', $nlinecounter); unset($question); $ignorerestofquestion = true; // Question Type not handled by Moodle. continue; } if ($ignorerestofquestion) { continue; } 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, $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 (!isset($question)) { continue; } if (isset($question->qtype) && 'calculated' == $question->qtype && preg_match("~^:([[: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; 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[$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. 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; 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; continue; } 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->answer[$currentchoice] = qformat_webct_convert_formula($webctoptions[1]); // Default settings. $question->fraction[$currentchoice] = 1.0; $question->tolerance[$currentchoice] = 0.0; $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($webctoptions[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 (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, $webctoptions)) { $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]; continue; } if (preg_match("~^:FEEDBACK([0-9]+):?~i", $line, $webctoptions)) { $generalfeedbacktext = ''; // Start gathering next lines. $currentchoice = $webctoptions[1]; continue; } if (preg_match('~^:FEEDBACK:(.*)~i', $line, $webctoptions)) { $generalfeedbacktext = ''; // Start gathering next lines. continue; } 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, $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, $webctoptions)) { // We can but hope that this always appear before the TOL property. $question->tolerance[$currentchoice] = 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. $question->tolerance[$currentchoice] /= 100; $question->tolerancetype[$currentchoice] = 1; // Relative. continue; } 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. foreach ($webctunits as $webctunit) { $unitrec->unit = trim($webctunit); $question->units[] = $unitrec; } continue; } 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; continue; } 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. continue; } if (isset($question->qtype) && 'calculated' == $question->qtype && preg_match('~^:ANSTYPE:dec~i', $line)) { $question->correctanswerformat[$currentchoice] = '1'; continue; } if (isset($question->qtype) && 'calculated' == $question->qtype && preg_match('~^:ANSTYPE:sig~i', $line)) { $question->correctanswerformat[$currentchoice] = '2'; continue; } } if (count($errors) > 0) { echo '<p>' . get_string('errorsdetected', 'qformat_webct', count($errors)) . '</p><ul>'; foreach ($errors as $error) { echo "<li>{$error}</li>"; } echo '</ul>'; unset($questions); // No questions imported. } if (count($warnings) > 0) { echo '<p>' . get_string('warningsdetected', 'qformat_webct', count($warnings)) . '</p><ul>'; foreach ($warnings as $warning) { echo "<li>{$warning}</li>"; } echo '</ul>'; } return $questions; }
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 (preg_match("~^:~", $line)) { $question->questiontext = trim($questiontext); unset($questiontext); } else { $questiontext .= str_replace('\\:', ':', $line); continue; } } if (isset($answertext) and is_string($answertext)) { if (preg_match("~^:~", $line)) { $answertext = 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 (preg_match("~^:~", $line)) { $question->subquestions[$currentchoice] = trim($responsetext); unset($responsetext); } else { $responsetext .= str_replace('\\:', ':', $line); continue; } } if (isset($feedbacktext) and is_string($feedbacktext)) { if (preg_match("~^:~", $line)) { $question->feedback[$currentchoice] = trim($feedbacktext); unset($feedbacktext); } else { $feedbacktext .= str_replace('\\:', ':', $line); continue; } } if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) { if (preg_match("~^:~", $line)) { $question->tempgeneralfeedback = trim($generalfeedbacktext); unset($generalfeedbacktext); } else { $generalfeedbacktext .= 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 // 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 (preg_match("~^:TYPE:MC:1(.*)~i", $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 (preg_match("~^:TYPE:MC:N(.*)~i", $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 (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; continue; } if (preg_match("~^:TYPE:C~i", $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 (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 continue; } if (preg_match("~^:TYPE:P~i", $line)) { // Paragraph Question $warnings[] = get_string("paragraphquestion", "quiz", $nLineCounter); unset($question); $ignore_rest_of_question = TRUE; // Question Type not handled by Moodle continue; } if (preg_match("~^:TYPE:~i", $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 (preg_match("~^:TITLE:(.*)~i", $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 = $name; continue; } if (preg_match("~^:IMAGE:(.*)~i", $line, $webct_options)) { $filename = trim($webct_options[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 (!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]) { 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 = 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; 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; continue; } if (preg_match('~^:FORMULA:(.*)~i', $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 (preg_match("~^:L([0-9]+)~i", $line, $webct_options)) { $answertext = ""; // Start gathering next lines $currentchoice = $webct_options[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]; continue; } if (preg_match("~^:REASON([0-9]+):?~i", $line, $webct_options)) { $feedbacktext = ""; // Start gathering next lines $currentchoice = $webct_options[1]; continue; } if (preg_match("~^:FEEDBACK([0-9]+):?~i", $line, $webct_options)) { $generalfeedbacktext = ""; // Start gathering next lines $currentchoice = $webct_options[1]; continue; } if (preg_match('~^:FEEDBACK:(.*)~i', $line, $webct_options)) { $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 ; 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]; 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 $question->tolerance[$currentchoice] = qformat_webct_convert_formula($webct_options[1]); continue; } if (isset($question->qtype) && CALCULATED == $question->qtype && preg_match('~^:TOLTYPE:percent~i', $line)) { // Percentage case is handled as relative in Moodle: $question->tolerance[$currentchoice] /= 100; $question->tolerancetype[$currentchoice] = 1; // Relative continue; } if (preg_match('~^:UNITS:(.+)~i', $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) && 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 $unitrec->unit = ''; $unitrec->multiplier = 1.0; $question->units[] = $unitrec; continue; } 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 continue; } if (isset($question->qtype) && CALCULATED == $question->qtype && preg_match('~^:ANSTYPE:dec~i', $line)) { $question->correctanswerformat[$currentchoice] = '1'; continue; } if (isset($question->qtype) && CALCULATED == $question->qtype && preg_match('~^:ANSTYPE:sig~i', $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; }