View source
<?php
class qformat_hotpot extends qformat_default {
function provide_import() {
return true;
}
function readquestions($lines) {
global $CFG, $COURSE, $course;
switch (true) {
case isset($this->course->id):
$courseid = $this->course->id;
break;
case isset($course->id):
$courseid = $course->id;
break;
case isset($COURSE->id):
$courseid = $COURSE->id;
break;
default:
$courseid = 0;
}
require_once $CFG->libdir . '/filelib.php';
$baseurl = get_file_url($courseid) . '/';
global $params;
if (isset($params) && !empty($params->choosefile)) {
$filename = $params->choosefile;
}
else {
$filename = basename($_FILES['newfile']['tmp_name']);
}
$source = implode($lines, " ");
$source = hotpot_convert_relative_urls($source, $baseurl, $filename);
$xml = new hotpot_xml_tree($source);
$xml->quiztype = '';
$keys = array_keys($xml->xml);
foreach ($keys as $key) {
if (preg_match('/^(hotpot|textoys)-(\\w+)-file$/i', $key, $matches)) {
$xml->quiztype = strtolower($matches[2]);
$xml->xml_root = "['{$key}']['#']";
break;
}
}
$questions = array();
switch ($xml->quiztype) {
case 'jcloze':
$this
->process_jcloze($xml, $questions);
break;
case 'jcross':
$this
->process_jcross($xml, $questions);
break;
case 'jmatch':
$this
->process_jmatch($xml, $questions);
break;
case 'jmix':
$this
->process_jmix($xml, $questions);
break;
case 'jbc':
case 'jquiz':
$this
->process_jquiz($xml, $questions);
break;
default:
if (empty($xml->quiztype)) {
notice("Input file not recognized as a Hot Potatoes XML file");
}
else {
notice("Unknown quiz type '{$xml->quiztype}'");
}
}
return $questions;
}
function process_jcloze(&$xml, &$questions) {
$defaultgrade = 1;
$gap_count = 0;
global $CFG, $db;
$moodle_14 = false;
if ($columns = $db
->MetaColumns("{$CFG->prefix}question_multianswer")) {
foreach ($columns as $column) {
if ($column->name == 'answers' || $column->name == 'positionkey' || $column->name == 'answertype' || $column->name == 'norm') {
$moodle_14 = true;
}
}
}
$tags = 'data,gap-fill';
$x = 0;
while (($exercise = "[{$x}]['#']") && $xml
->xml_value($tags, $exercise)) {
if (method_exists($this, 'defaultquestion')) {
$question = $this
->defaultquestion();
}
else {
$question = new stdClass();
$question->usecase = 0;
$question->image = "";
}
$question->qtype = MULTIANSWER;
$question->name = $this
->hotpot_get_title($xml, $x);
$question->questiontext = $this
->hotpot_get_reading($xml);
if ($moodle_14) {
$question->answers = array();
}
else {
global $COURSE;
$question->course = $COURSE->id;
$question->options = new stdClass();
$question->options->questions = array();
}
$q = 0;
while ($text = $xml
->xml_value($tags, $exercise . "[{$q}]")) {
$question->questiontext .= $this
->hotpot_prepare_str($text);
$question_record = $exercise . "['question-record'][{$q}]['#']";
if ($xml
->xml_value($tags, $question_record)) {
$gap_count++;
$positionkey = $q + 1;
$question->questiontext .= '{#' . $positionkey . '}';
if ($moodle_14) {
$question->answers[$q]->positionkey = $positionkey;
$question->answers[$q]->answertype = SHORTANSWER;
$question->answers[$q]->norm = $defaultgrade;
$question->answers[$q]->alternatives = array();
}
else {
$wrapped = new stdClass();
$wrapped->qtype = SHORTANSWER;
$wrapped->usecase = 0;
$wrapped->defaultgrade = $defaultgrade;
$wrapped->questiontextformat = 0;
$wrapped->answer = array();
$wrapped->fraction = array();
$wrapped->feedback = array();
$answers = array();
}
$a = 0;
while (($answer = $question_record . "['answer'][{$a}]['#']") && $xml
->xml_value($tags, $answer)) {
$text = $this
->hotpot_prepare_str($xml
->xml_value($tags, $answer . "['text'][0]['#']"));
$correct = $xml
->xml_value($tags, $answer . "['correct'][0]['#']");
$feedback = $this
->hotpot_prepare_str($xml
->xml_value($tags, $answer . "['feedback'][0]['#']"));
if ($text) {
$fraction = empty($correct) ? 0 : 1;
if ($moodle_14) {
$question->answers[$q]->alternatives[$a] = new stdClass();
$question->answers[$q]->alternatives[$a]->answer = $text;
$question->answers[$q]->alternatives[$a]->fraction = $fraction;
$question->answers[$q]->alternatives[$a]->feedback = $feedback;
}
else {
$wrapped->answer[] = $text;
$wrapped->fraction[] = $fraction;
$wrapped->feedback[] = $feedback;
$answers[] = (empty($fraction) ? '' : '=') . $text . (empty($feedback) ? '' : '#' . $feedback);
}
}
$a++;
}
if ($moodle_14) {
}
else {
$wrapped->questiontext = '{' . $defaultgrade . ':SHORTANSWER:' . implode('~', $answers) . '}';
$question->options->questions[] = $wrapped;
}
}
$q++;
}
$question->defaultgrade = $gap_count * $defaultgrade;
$questions[] = $question;
$x++;
}
}
function process_jcross(&$xml, &$questions) {
$tags = 'data,crossword,clues,item';
$x = 0;
while (($item = "[{$x}]['#']") && $xml
->xml_value($tags, $item)) {
$text = $xml
->xml_value($tags, $item . "['def'][0]['#']");
$answer = $xml
->xml_value($tags, $item . "['word'][0]['#']");
if ($text && $answer) {
if (method_exists($this, 'defaultquestion')) {
$question = $this
->defaultquestion();
}
else {
$question = new stdClass();
$question->usecase = 0;
$question->image = "";
}
$question->qtype = SHORTANSWER;
$question->name = $this
->hotpot_get_title($xml, $x, true);
$question->questiontext = $this
->hotpot_prepare_str($text);
$question->answer = array(
$this
->hotpot_prepare_str($answer),
);
$question->fraction = array(
1,
);
$question->feedback = array(
'',
);
$questions[] = $question;
}
$x++;
}
}
function process_jmatch(&$xml, &$questions) {
$defaultgrade = 1;
$match_count = 0;
$tags = 'data,matching-exercise';
$x = 0;
while (($exercise = "[{$x}]['#']") && $xml
->xml_value($tags, $exercise)) {
if (method_exists($this, 'defaultquestion')) {
$question = $this
->defaultquestion();
}
else {
$question = new stdClass();
$question->usecase = 0;
$question->image = "";
}
$question->qtype = MATCH;
$question->name = $this
->hotpot_get_title($xml, $x);
$question->questiontext = $this
->hotpot_get_reading($xml);
$question->questiontext .= $this
->hotpot_get_instructions($xml);
$question->subquestions = array();
$question->subanswers = array();
$p = 0;
while (($pair = $exercise . "['pair'][{$p}]['#']") && $xml
->xml_value($tags, $pair)) {
$left = $xml
->xml_value($tags, $pair . "['left-item'][0]['#']['text'][0]['#']");
$right = $xml
->xml_value($tags, $pair . "['right-item'][0]['#']['text'][0]['#']");
if ($left && $right) {
$match_count++;
$question->subquestions[$p] = $this
->hotpot_prepare_str($left);
$question->subanswers[$p] = $this
->hotpot_prepare_str($right);
}
$p++;
}
$question->defaultgrade = $match_count * $defaultgrade;
$questions[] = $question;
$x++;
}
}
function process_jmix(&$xml, &$questions) {
$defaultgrade = 1;
$segment_count = 0;
$tags = 'data,jumbled-order-exercise';
$x = 0;
while (($exercise = "[{$x}]['#']") && $xml
->xml_value($tags, $exercise)) {
if (method_exists($this, 'defaultquestion')) {
$question = $this
->defaultquestion();
}
else {
$question = new stdClass();
$question->usecase = 0;
$question->image = "";
}
$question->qtype = SHORTANSWER;
$question->name = $this
->hotpot_get_title($xml, $x);
$question->answer = array();
$question->fraction = array();
$question->feedback = array();
$i = 0;
$segments = array();
while ($segment = $xml
->xml_value($tags, $exercise . "['main-order'][0]['#']['segment'][{$i}]['#']")) {
$segments[] = $this
->hotpot_prepare_str($segment);
$segment_count++;
$i++;
}
$answer = implode(' ', $segments);
$this
->hotpot_seed_RNG();
shuffle($segments);
$question->questiontext = $this
->hotpot_get_reading($xml);
$question->questiontext .= $this
->hotpot_get_instructions($xml);
$question->questiontext .= ' <NOBR><B>[ ' . implode(' ', $segments) . ' ]</B></NOBR>';
$a = 0;
while (!empty($answer)) {
$question->answer[$a] = $answer;
$question->fraction[$a] = 1;
$question->feedback[$a] = '';
$answer = $this
->hotpot_prepare_str($xml
->xml_value($tags, $exercise . "['alternate'][{$a}]['#']"));
$a++;
}
$question->defaultgrade = $segment_count * $defaultgrade;
$questions[] = $question;
$x++;
}
}
function process_jquiz(&$xml, &$questions) {
$defaultgrade = 1;
$tags = 'data,questions';
$x = 0;
while (($exercise = "[{$x}]['#']") && $xml
->xml_value($tags, $exercise)) {
$q = 0;
while (($question_record = $exercise . "['question-record'][{$q}]['#']") && $xml
->xml_value($tags, $question_record)) {
if (method_exists($this, 'defaultquestion')) {
$question = $this
->defaultquestion();
}
else {
$question = new stdClass();
$question->usecase = 0;
$question->image = "";
}
$question->defaultgrade = $defaultgrade;
$question->name = $this
->hotpot_get_title($xml, $q, true);
$text = $xml
->xml_value($tags, $question_record . "['question'][0]['#']");
$question->questiontext = $this
->hotpot_prepare_str($text);
if ($xml
->xml_value($tags, $question_record . "['answers']")) {
$answers = $question_record . "['answers'][0]['#']";
}
else {
$answers = $question_record;
}
if ($xml
->xml_value($tags, $question_record . "['question-type']")) {
$type = $xml
->xml_value($tags, $question_record . "['question-type'][0]['#']");
}
else {
switch ($xml->quiztype) {
case 'jbc':
$must_select_all = $xml
->xml_value($tags, $question_record . "['must-select-all'][0]['#']");
if (empty($must_select_all)) {
$type = 1;
}
else {
$type = 4;
}
break;
case 'jquiz':
$type = 2;
break;
default:
$type = 0;
}
}
$question->qtype = $type == 2 ? SHORTANSWER : MULTICHOICE;
$question->single = $type == 4 ? 0 : 1;
$no_of_correct_answers = 0;
if ($type == 4) {
$a = 0;
while (($answer = $answers . "['answer'][{$a}]['#']") && $xml
->xml_value($tags, $answer)) {
$correct = $xml
->xml_value($tags, $answer . "['correct'][0]['#']");
if (empty($correct)) {
}
else {
$no_of_correct_answers++;
}
$a++;
}
}
$a = 0;
$question->answer = array();
$question->fraction = array();
$question->feedback = array();
$aa = 0;
$correct_answers = array();
$correct_answers_all_zero = true;
while (($answer = $answers . "['answer'][{$a}]['#']") && $xml
->xml_value($tags, $answer)) {
$correct = $xml
->xml_value($tags, $answer . "['correct'][0]['#']");
if (empty($correct)) {
$fraction = 0;
}
else {
if ($type == 4) {
$fraction = round(1 / $no_of_correct_answers, 5);
}
else {
if ($xml
->xml_value($tags, $answer . "['percent-correct']")) {
$percent = $xml
->xml_value($tags, $answer . "['percent-correct'][0]['#']");
$fraction = $percent / 100;
}
else {
$fraction = 1;
}
}
}
$answertext = $this
->hotpot_prepare_str($xml
->xml_value($tags, $answer . "['text'][0]['#']"));
if ($answertext != '') {
$question->answer[$aa] = $answertext;
$question->fraction[$aa] = $fraction;
$question->feedback[$aa] = $this
->hotpot_prepare_str($xml
->xml_value($tags, $answer . "['feedback'][0]['#']"));
if ($correct) {
if ($fraction) {
$correct_answers_all_zero = false;
}
$correct_answers[] = $aa;
}
$aa++;
}
$a++;
}
if ($correct_answers_all_zero) {
foreach ($correct_answers as $aa) {
$question->fraction[$aa] = 1;
}
}
if (!empty($question->questiontext)) {
$questions[] = $question;
}
$q++;
}
$x++;
}
}
function hotpot_seed_RNG() {
static $HOTPOT_SEEDED_RNG = FALSE;
if (!$HOTPOT_SEEDED_RNG) {
srand((double) microtime() * 1000000);
$HOTPOT_SEEDED_RNG = TRUE;
}
}
function hotpot_get_title(&$xml, $x, $flag = false) {
$title = $xml
->xml_value('data,title');
if ($x || $flag) {
$title .= ' (' . ($x + 1) . ')';
}
return $this
->hotpot_prepare_str($title);
}
function hotpot_get_instructions(&$xml) {
$text = $xml
->xml_value('hotpot-config-file,instructions');
if (empty($text)) {
$text = "Hot Potatoes {$xml->quiztype}";
}
return $this
->hotpot_prepare_str($text);
}
function hotpot_get_reading(&$xml) {
$str = '';
$tags = 'data,reading';
if ($xml
->xml_value("{$tags},include-reading")) {
if ($title = $xml
->xml_value("{$tags},reading-title")) {
$str .= "<H3>{$title}</H3>";
}
if ($text = $xml
->xml_value("{$tags},reading-text")) {
$str .= "<P>{$text}</P>";
}
}
return $this
->hotpot_prepare_str($str);
}
function hotpot_prepare_str($str) {
$str = preg_replace('/&#x([0-9a-f]+);/ie', "hotpot_charcode_to_utf8(hexdec('\\1'))", $str);
$str = preg_replace('/&#([0-9]+);/e', "hotpot_charcode_to_utf8(\\1)", $str);
return addslashes($str);
}
}
require_once "{$CFG->libdir}/xmlize.php";
class hotpot_xml_tree {
function hotpot_xml_tree($str, $xml_root = '') {
if (empty($str)) {
$this->xml = array();
}
else {
$this
->encode_cdata($str, 'gap-fill');
$this->xml = xmlize($str, 0);
}
$this->xml_root = $xml_root;
}
function xml_value($tags, $more_tags = "[0]['#']") {
$tags = empty($tags) ? '' : "['" . str_replace(",", "'][0]['#']['", $tags) . "']";
eval('$value = &$this->xml' . $this->xml_root . $tags . $more_tags . ';');
if (is_string($value)) {
$value = strtr($value, array(
'<' => '<',
'>' => '>',
'&' => '&',
));
$htmltags = '(' . 'TABLE|/?CAPTION|/?COL|/?COLGROUP|/?TBODY|/?TFOOT|/?THEAD|/?TD|/?TH|/?TR' . '|OL|UL|/?LI' . '|DL|/?DT|/?DD' . '|EMBED|OBJECT|APPLET|/?PARAM' . ')';
$search = '#(<' . $htmltags . '[^>]*' . '>)\\s+' . '(?=' . '<' . ')#is';
$value = preg_replace($search, '\\1', $value);
$value = str_replace("\n", '<br />', $value);
$search = '#(' . '[\\xc0-\\xdf][\\x80-\\xbf]' . '|' . '[\\xe0-\\xef][\\x80-\\xbf]{2}' . '|' . '[\\xf0-\\xff][\\x80-\\xbf]{3}' . '|' . '[\\x80-\\xff]' . ')#se';
$value = preg_replace($search, "hotpot_utf8_to_html_entity('\\1')", $value);
}
return $value;
}
function encode_cdata(&$str, $tag) {
static $HTML_ENTITIES = array(
''' => "'",
'"' => '"',
'<' => '<',
'>' => '>',
'&' => '&',
);
static $ILLEGAL_STRINGS = array(
"\r" => '',
"\n" => '<br />',
']]>' => ']]>',
);
$pattern = '|(^.*<' . $tag . '[^>]*)(>.*<)(/' . $tag . '>.*$)|is';
if (preg_match($pattern, $str, $matches)) {
$matches[2] = strtr($matches[2], $ILLEGAL_STRINGS);
$search = '/>([^<]*&[^<]*)</e';
$replace = '"><![CDATA[".strtr("$1", $HTML_ENTITIES)."]]><"';
$matches[2] = preg_replace($search, $replace, $matches[2]);
$str = $matches[1] . $matches[2] . $matches[3];
}
}
}
function hotpot_charcode_to_utf8($charcode) {
if ($charcode <= 0x7f) {
return chr($charcode);
}
if ($charcode <= 0x7ff) {
return chr(($charcode >> 0x6) + 0xc0) . chr(($charcode & 0x3f) + 128);
}
if ($charcode <= 0xffff) {
return chr(($charcode >> 0xc) + 0xe0) . chr(($charcode >> 0x6 & 0x3f) + 0x80) . chr(($charcode & 0x3f) + 0x80);
}
if ($charcode <= 0x1fffff) {
return chr(($charcode >> 0x12) + 0xf0) . chr(($charcode >> 0xc & 0x3f) + 0x80) . chr(($charcode >> 0x6 & 0x3f) + 0x80) . chr(($charcode & 0x3f) + 0x80);
}
return ' ';
}
function hotpot_utf8_to_html_entity($char) {
static $HOTPOT_UTF8_DECREMENT = array(
1 => 0,
2 => 192,
3 => 224,
4 => 240,
);
static $HOTPOT_UTF8_SHIFT = array(
1 => array(
0 => 0,
),
2 => array(
0 => 6,
1 => 0,
),
3 => array(
0 => 12,
1 => 6,
2 => 0,
),
4 => array(
0 => 18,
1 => 12,
2 => 6,
3 => 0,
),
);
$dec = 0;
$len = strlen($char);
for ($pos = 0; $pos < $len; $pos++) {
$ord = ord($char[$pos]);
$ord -= $pos ? 128 : $HOTPOT_UTF8_DECREMENT[$len];
$dec += $ord << $HOTPOT_UTF8_SHIFT[$len][$pos];
}
return '&#x' . sprintf('%04X', $dec) . ';';
}
function hotpot_convert_relative_urls($str, $baseurl, $filename) {
$tagopen = '(?:(<)|(<)|(&#x003C;))';
$tagclose = '(?(2)>|(?(3)>|(?(4)&#x003E;)))';
$space = '\\s+';
$anychar = '(?:[^>]*?)';
$quoteopen = '("|"|&quot;)';
$quoteclose = '\\5';
$replace = "hotpot_convert_relative_url('" . $baseurl . "', '" . $filename . "', '\\1', '\\6', '\\7')";
$tags = array(
'script' => 'src',
'link' => 'href',
'a' => 'href',
'img' => 'src',
'param' => 'value',
'object' => 'data',
'embed' => 'src',
);
foreach ($tags as $tag => $attribute) {
if ($tag == 'param') {
$url = '\\S+?\\.\\S+?';
}
else {
$url = '.*?';
}
$search = "%({$tagopen}{$tag}{$space}{$anychar}{$attribute}={$quoteopen})({$url})({$quoteclose}{$anychar}{$tagclose})%ise";
$str = preg_replace($search, $replace, $str);
}
return $str;
}
function hotpot_convert_relative_url($baseurl, $filename, $opentag, $url, $closetag, $stripslashes = true) {
if ($stripslashes) {
$opentag = stripslashes($opentag);
$url = stripslashes($url);
$closetag = stripslashes($closetag);
}
if (preg_match('|^' . '\\w+=[^&]+' . '(' . '&((amp;#x0026;)?amp;)?' . '\\w+=[^&]+)*' . '$|', $url)) {
$query = $url;
$url = '';
$fragment = '';
}
else {
if (preg_match('|^' . '([^?]*)' . '((?:\\?[^#]*)?)' . '((?:#.*)?)' . '$|', $url, $matches)) {
$url = $matches[1];
$query = $matches[2];
$fragment = $matches[3];
}
else {
$query = '';
$fragment = '';
}
}
if ($url) {
$url = hotpot_convert_url($baseurl, $filename, $url, false);
}
if ($query) {
$search = '#' . '(file|src|thesound)=' . "([^&]+)" . '#ise';
$replace = "'\\1='.hotpot_convert_url('" . $baseurl . "','" . $filename . "','\\2')";
$query = preg_replace($search, $replace, $query);
}
$url = $opentag . $url . $query . $fragment . $closetag;
return $url;
}
function hotpot_convert_url($baseurl, $filename, $url, $stripslashes = true) {
static $HOTPOT_RELATIVE_URLS = array();
if ($stripslashes) {
$url = stripslashes($url);
}
if (preg_match('%^(http://|/|javascript:)%i', $url)) {
}
else {
if (isset($HOTPOT_RELATIVE_URLS[$url])) {
$url = $HOTPOT_RELATIVE_URLS[$url];
}
else {
$relativeurl = $url;
$dir = dirname($filename);
while (preg_match('|^(\\.{1,2})/(.*)$|', $url, $matches)) {
if ($matches[1] == '..') {
$dir = dirname($dir);
}
$url = $matches[2];
}
if ($dir && $dir != '.') {
$baseurl .= "{$dir}/";
}
$url = "{$baseurl}{$url}";
$HOTPOT_RELATIVE_URLS[$relativeurl] = $url;
}
}
return $url;
}
if (!class_exists("quiz_file_format")) {
class quiz_file_format extends qformat_default {
function readquestions($lines) {
$format = new qformat_hotpot();
return $format
->readquestions($lines);
}
}
}