From 43bf9fc2d71450ec1fb91cd739814a5b20dad12a Mon Sep 17 00:00:00 2001 From: moodler Date: Fri, 12 Dec 2003 07:35:55 +0000 Subject: [PATCH] Reworking of the plug-in format for import/export files. Each format is now in it's own subdirectory, which allows the format to have as many files as it likes. The formats are now detected automatically (no need to edit lib.php anymore) and the parent class is now in mod/quiz/format.php Lastly, I added Paul Shew's new GIFT format. --- mod/quiz/{format/default.php => format.php} | 4 +- mod/quiz/format/README | 6 - mod/quiz/format/README.txt | 12 + mod/quiz/format/aiken/format.php | 91 ++++ mod/quiz/format/aon/format.php | 224 ++++++++ mod/quiz/format/blackboard/format.php | 252 +++++++++ mod/quiz/format/coursetestmanager/format.php | 507 +++++++++++++++++++ mod/quiz/format/custom.php | 25 - mod/quiz/format/gift/format.php | 274 ++++++++++ mod/quiz/format/missingword/format.php | 156 ++++++ mod/quiz/format/multianswer/format.php | 162 ++++++ mod/quiz/format/qti/format.php | 19 + mod/quiz/format/webct/format.php | 19 + mod/quiz/import.php | 21 +- mod/quiz/lib.php | 8 - 15 files changed, 1734 insertions(+), 46 deletions(-) rename mod/quiz/{format/default.php => format.php} (97%) delete mode 100644 mod/quiz/format/README create mode 100644 mod/quiz/format/README.txt create mode 100644 mod/quiz/format/aiken/format.php create mode 100644 mod/quiz/format/aon/format.php create mode 100644 mod/quiz/format/blackboard/format.php create mode 100755 mod/quiz/format/coursetestmanager/format.php delete mode 100644 mod/quiz/format/custom.php create mode 100755 mod/quiz/format/gift/format.php create mode 100644 mod/quiz/format/missingword/format.php create mode 100644 mod/quiz/format/multianswer/format.php create mode 100644 mod/quiz/format/qti/format.php create mode 100644 mod/quiz/format/webct/format.php diff --git a/mod/quiz/format/default.php b/mod/quiz/format.php similarity index 97% rename from mod/quiz/format/default.php rename to mod/quiz/format.php index 0145c54c7d..2af24e200c 100644 --- a/mod/quiz/format/default.php +++ b/mod/quiz/format.php @@ -1,12 +1,12 @@ 2 November 2003 +/// + +//////////////////////////////////////////////////////////////////////////// +/// AIKEN FORMAT +/// +/// This Moodle class provides all functions necessary to import and export +/// one-correct-answer multiple choice questions in this format: +/// +/// Question text +/// A) Choice #1 +/// B) Choice #2 +/// C) Choice #3 +/// D) Choice #4 +/// ANSWER: B +/// (blank line next not necessary since "AN" at the beginning of a line +/// triggers the question input and causes input to start all over. +/// +///Only ONE correct answer is allowed with no feedback responses. +/// +///Be sure to reword "All of the above" type questions as "All of these" (etc.) so that choices can +/// be randomized +/// +///This should work on WIN, Mac and Unix although only tested on Mac +/// +//////////////////////////////////////////////////////////////////////////// + +// Based on format.php, included by ../../import.php + +class quiz_file_format extends quiz_default_format { + +//will this override default function? + function readquestions($lines){ + $questions = array(); + $endchar = chr(13); + foreach ($lines as $line) { + $stp = strpos($line,$endchar,0); + $newlines = explode($endchar,$line); + $foundQ = 0; + for ($i=0; $i < count($newlines);$i++){ + $nowline = addslashes($newlines[$i]); + ///Go through the arrage and build an object called $question + ///When done, add $question to $questions + if (strlen($nowline)< 2) { + continue; + } + // This will show everyline when file is being processed + // print("$nowline
"); + $leader = substr(ltrim($nowline),0,2); + if (strpos(".A)B)C)D)E)F)G)H)I)J)A.B.C.D.E.F.G.H.I.J.",$leader)>0){ + //trim off the label and space + $question->answer[] = substr($nowline,3); + $question->fraction[] = 0; + continue; + } + if ($leader == "AN"){ + $ans = trim(strstr($nowline,":")); + $ans = substr($ans,2,1); + //A becomes 0 since array starts from 0 + $rightans = ord($ans) - 65; + $question->fraction[$rightans] = 1; + $questions[] = $question; + //clear array for next question set + $question = NULL; + continue; + } else { + //Must be the first line since no leader + $question->qtype = MULTICHOICE; + $question->name = substr($nowline,0,50); + $question->questiontext = $nowline; + $question->single = 1; + $question->feedback[] = ""; + $question->usecase = 0; // Ignore case + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + } + } + } + return $questions; + } + + function readquestion($lines) { + //this is no longer needed but might still be called by default.php + return; + } +} + +?> diff --git a/mod/quiz/format/aon/format.php b/mod/quiz/format/aon/format.php new file mode 100644 index 0000000000..c9c39af137 --- /dev/null +++ b/mod/quiz/format/aon/format.php @@ -0,0 +1,224 @@ +displayerrors) { + echo "

$text

Could not find a {"; + } + return false; + } + + $answerfinish = strpos($text, "}"); + if ($answerfinish === false) { + if ($this->displayerrors) { + echo "

$text

Could not find a }"; + } + return false; + } + + $answerlength = $answerfinish - $answerstart; + $answertext = substr($text, $answerstart + 1, $answerlength - 1); + + /// Save the new question text + $question->questiontext = addslashes(substr_replace($text, "_____", $answerstart, $answerlength+1)); + $question->name = substr($question->questiontext, 0, 60)." ..."; + + + /// Parse the answers + $answers = explode("~", $answertext); + + $countanswers = count($answers); + + switch ($countanswers) { + case 0: // invalid question + if ($this->displayerrors) { + echo "

No answers found in $answertext"; + } + return false; + + case 1: + $question->qtype = SHORTANSWER; + + $answer = trim($answers[0]); + if ($answer[0] == "=") { + $answer = substr($answer, 1); + } + $question->answer[] = addslashes($answer); + $question->fraction[] = 1; + $question->feedback[] = ""; + + $question->usecase = 0; // Ignore case + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + return $question; + + default: + $question->qtype = MULTICHOICE; + + $answers = swapshuffle($answers); + foreach ($answers as $key => $answer) { + $answer = trim($answer); + if ($answer[0] == "=") { + $question->fraction[$key] = 1; + $answer = substr($answer, 1); + } else { + $question->fraction[$key] = 0; + } + $question->answer[$key] = addslashes($answer); + $question->feedback[$key] = ""; + } + + $question->defaultgrade = 1; + $question->single = 1; // Only one answer is allowed + $question->image = ""; // No images with this format + return $question; + } + } + + function importpostprocess() { + /// Goes through the questionids, looking for shortanswer questions + /// and converting random groups of 4 into matching questions. + + /// Doesn't handle shortanswer questions with more than one answer + + global $CFG; + + print_heading(count($this->questionids)." ".get_string("questions", "quiz")); + + $questionids = implode(',', $this->questionids); + + if (!$shortanswers = get_records_select("quiz_questions", + "id IN ($questionids) AND qtype = ".SHORTANSWER, + "", "id,qtype")) { + return true; + } + + + $shortanswerids = array(); + foreach ($shortanswers as $key => $shortanswer) { + $shortanswerids[] = $key; + } + + $strmatch = get_string("match", "quiz")." (".$this->category->name.")"; + + $shortanswerids = swapshuffle($shortanswerids); + $count = $shortanswercount = count($shortanswerids); + $i = 1; + $matchcount = 0; + + $question->category = $this->category->id; + $question->qtype = MATCH; + $question->questiontext = get_string("randomsamatchintro", "quiz"); + $question->image = ""; + + while ($count > 4) { + $matchcount++; + $question->name = "$strmatch $i"; + $question->subquestions = array(); + $question->subanswers = array(); + + $extractids = implode(',', array_splice($shortanswerids, -4)); + $count = count($shortanswerids); + + $extracts = get_records_sql("SELECT q.questiontext, a.answer + FROM {$CFG->prefix}quiz_questions q, + {$CFG->prefix}quiz_shortanswer sa, + {$CFG->prefix}quiz_answers a + WHERE q.id in ($extractids) + AND sa.question = q.id + AND a.id = sa.answers"); + + if (count($extracts) != 4) { + print_object($extracts); + notify("Could not find exactly four shortanswer questions with ids: $extractids"); + continue; + } + + $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) + $question->version = 1; // Original version of this question + + if (!$question->id = insert_record("quiz_questions", $question)) { + error("Could not insert new question!"); + } + + foreach ($extracts as $shortanswer) { + $question->subquestions[] = addslashes($shortanswer->questiontext); + $question->subanswers[] = addslashes($shortanswer->answer); + } + + $result = quiz_save_question_options($question); + + if (!empty($result->error)) { + notify("Error: $result->error"); + } + + if (!empty($result->notice)) { + notify($result->notice); + } + + /// Delete the old short-answer questions + + execute_sql("DELETE FROM {$CFG->prefix}quiz_questions WHERE id IN ($extractids)", false); + execute_sql("DELETE FROM {$CFG->prefix}quiz_shortanswer WHERE question IN ($extractids)", false); + execute_sql("DELETE FROM {$CFG->prefix}quiz_answers WHERE question IN ($extractids)", false); + + } + + if ($count) { /// Delete the remaining ones + foreach ($shortanswerids as $shortanswerid) { + delete_records("quiz_questions", "id", $shortanswerid); + delete_records("quiz_shortanswer", "question", $shortanswerid); + delete_records("quiz_answers", "question", $shortanswerid); + } + } + $info = "$shortanswercount ".get_string("shortanswer", "quiz"). + " => $matchcount ".get_string("match", "quiz"); + + print_heading($info); + + $options['category'] = $this->category->id; + echo "

"; + print_single_button("multiple.php", $options, get_string("randomcreate", "quiz")); + echo "
"; + + return true; + } + +} + +?> diff --git a/mod/quiz/format/blackboard/format.php b/mod/quiz/format/blackboard/format.php new file mode 100644 index 0000000000..66f72efb2f --- /dev/null +++ b/mod/quiz/format/blackboard/format.php @@ -0,0 +1,252 @@ +libdir/xmlize.php"); + +class quiz_file_format extends quiz_default_format { + +/******************************** + Need to re-compile php with zip support before testing this + + function readdata($filename) { + /// Returns complete file with an array, one item per line + + if (is_readable($filename)) { + + $zip = zip_open($filename); + $zip_entry = $zip_read($zip); + if (strstr($zip_entry_name($zip_entry), "imsmanifest") == 0) + $zip_entry = $zip_read($zip); // skip past manifest file + + if (zip_entry_open($zip, $zip_entry, "r")) { + + $strbuf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)); + $buf = explode("\n", $strbuf); + zip_entry_close($zip_entry); + zip_close($zip); + return $buf; + + } else { + + zip_close($zip); + return false; + + } + + } + + return false; + } + +********************************/ + + function readquestions ($lines) { + /// Parses an array of lines into an array of questions, + /// where each item is a question object as defined by + /// readquestion(). + + $text = implode($lines, " "); + $xml = xmlize($text); + + $questions = array(); + + process_tf($xml, $questions); + process_mc($xml, $questions); + process_fib($xml, $questions); + process_matching($xml, $questions); + + return $questions; + } +} + +//---------------------------------------- +// Process True / False Questions +//---------------------------------------- +function process_tf($xml, &$questions) { + + $tfquestions = $xml["POOL"]["#"]["QUESTION_TRUEFALSE"]; + + for ($i = 0; $i < sizeof ($tfquestions); $i++) { + + $question = NULL; + + $question->qtype = TRUEFALSE; + $question->defaultgrade = 1; + $question->single = 1; // Only one answer is allowed + $question->image = ""; // No images with this format + + $thisquestion = $tfquestions[$i]; + // put questiontext in question object + $question->questiontext = addslashes(trim($thisquestion["#"]["BODY"][0]["#"]["TEXT"][0]["#"])); + // put name in question object + $question->name = $question->questiontext; + + $choices = $thisquestion["#"]["ANSWER"]; + + $correct_answer = $thisquestion["#"]["GRADABLE"][0]["#"]["CORRECTANSWER"][0]["@"]["answer_id"]; + + // first choice is true, second is false. + $id = $choices[0]["@"]["id"]; + + if (strcmp($id, $correct_answer) == 0) { // true is correct + $question->answer = 1; + $question->feedbacktrue = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_CORRECT"][0]["#"])); + $question->feedbackfalse = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_INCORRECT"][0]["#"])); + } else { // false is correct + $question->answer = 0; + $question->feedbacktrue = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_INCORRECT"][0]["#"])); + $question->feedbackfalse = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_CORRECT"][0]["#"])); + } + $questions[] = $question; + } +} + +//---------------------------------------- +// Process Multiple Choice Questions +//---------------------------------------- +function process_mc($xml, &$questions) { + + $mcquestions = $xml["POOL"]["#"]["QUESTION_MULTIPLECHOICE"]; + + for ($i = 0; $i < sizeof ($mcquestions); $i++) { + + $question = NULL; + + $question->qtype = MULTICHOICE; + $question->defaultgrade = 1; + $question->single = 1; // Only one answer is allowed + $question->image = ""; // No images with this format + + $thisquestion = $mcquestions[$i]; + // put questiontext in question object + $question->questiontext = addslashes(trim($thisquestion["#"]["BODY"][0]["#"]["TEXT"][0]["#"])); + // put name of question in question object + $question->name = $question->questiontext; + + $choices = $thisquestion["#"]["ANSWER"]; + for ($j = 0; $j < sizeof ($choices); $j++) { + + $choice = trim($choices[$j]["#"]["TEXT"][0]["#"]); + // put this choice in the question object. + $question->answer[$j] = addslashes($choice); + + $id = $choices[$j]["@"]["id"]; + $correct_answer_id = $thisquestion["#"]["GRADABLE"][0]["#"]["CORRECTANSWER"][0]["@"]["answer_id"]; + // if choice is the answer, give 100%, otherwise give 0% + if (strcmp ($id, $correct_answer_id) == 0) { + $question->fraction[$j] = 1; + $question->feedback[$j] = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_CORRECT"][0]["#"])); + } else { + $question->fraction[$j] = 0; + $question->feedback[$j] = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_INCORRECT"][0]["#"])); + } + } + $questions[] = $question; + } +} + +//---------------------------------------- +// Process Fill in the Blank Questions +//---------------------------------------- +function process_fib($xml, &$questions) { + + $fibquestions = $xml["POOL"]["#"]["QUESTION_FILLINBLANK"]; + for ($i = 0; $i < sizeof ($fibquestions); $i++) { + + $question = NULL; + + $question->qtype = SHORTANSWER; + $question->defaultgrade = 1; + $question->usecase = 0; // Ignore case + $question->image = ""; // No images with this format + + $thisquestion = $fibquestions[$i]; + // put questiontext in question object + $question->questiontext = addslashes(trim($thisquestion["#"]["BODY"][0]["#"]["TEXT"][0]["#"])); + // put name of question in question object + $question->name = $question->questiontext; + + $answer = trim($thisquestion["#"]["ANSWER"][0]["#"]["TEXT"][0]["#"]); + + $question->answer[] = addslashes($answer); + $question->fraction[] = 1; + $question->feedback[0] = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_CORRECT"][0]["#"])); + $question->feedback[1] = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_INCORRECT"][0]["#"])); + + $questions[] = $question; + } +} + +//---------------------------------------- +// Process Matching Questions +//---------------------------------------- +function process_matching($xml, &$questions) { + + $matchquestions = $xml["POOL"]["#"]["QUESTION_MATCH"]; + for ($i = 0; $i < sizeof ($matchquestions); $i++) { + + $question = NULL; + + $question->qtype = MATCH; + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + + $thisquestion = $matchquestions[$i]; + // put questiontext in question object + $question->questiontext = addslashes(trim($thisquestion["#"]["BODY"][0]["#"]["TEXT"][0]["#"])); + // put name of question in question object + $question->name = $question->questiontext; + + $choices = $thisquestion["#"]["CHOICE"]; + for ($j = 0; $j < sizeof ($choices); $j++) { + + $subquestion = NULL; + + $choice = $choices[$j]["#"]["TEXT"][0]["#"]; + $choice_id = $choices[$j]["@"]["id"]; + + $question->subanswers[] = addslashes(trim($choice)); + + $correctanswers = $thisquestion["#"]["GRADABLE"][0]["#"]["CORRECTANSWER"]; + for ($k = 0; $k < sizeof ($correctanswers); $k++) { + + if (strcmp($choice_id, $correctanswers[$k]["@"]["choice_id"]) == 0) { + + $answer_id = $correctanswers[$k]["@"]["answer_id"]; + + $answers = $thisquestion["#"]["ANSWER"]; + for ($m = 0; $m < sizeof ($answers); $m++) { + + $answer = $answers[$m]; + $current_ans_id = $answer["@"]["id"]; + if (strcmp ($current_ans_id, $answer_id) == 0) { + + $answer = $answer["#"]["TEXT"][0]["#"]; + $question->subquestions[] = addslashes(trim($answer)); + break; + + } + + } + + break; + + } + + } + + } + + $questions[] = $question; + + } +} +?> diff --git a/mod/quiz/format/coursetestmanager/format.php b/mod/quiz/format/coursetestmanager/format.php new file mode 100755 index 0000000000..4ba52ef9e7 --- /dev/null +++ b/mod/quiz/format/coursetestmanager/format.php @@ -0,0 +1,507 @@ +category = $category; // Important + return true; + } + function importprocess($filename) { + global $CFG,$QUIZ_FILE_FORMAT,$strimportquestions,$form,$question_category,$category,$course,$THEME, + $hostname, $mdapath, $mdbpath; + if ((PHP_OS == "Linux") and isset($hostname)) { + $hostname = trim($hostname); + // test the ODBC socket server connection + // if failure, unset hostname and set hostname_access_error + $question_categories = $this->getquestioncategories($mdbpath, $mdapath, $hostname); + if (!$question_categories) { + $hostname_access_error = $hostname . " "; + unset($hostname); + } else { + $hostname_access_error = 0; + } + } + + if ((PHP_OS == "Linux") and !isset($hostname)) { + // copy the file to a semi-permanent location + if (! $basedir = make_upload_directory("$course->id")) { + error("The site administrator needs to fix the file permissions for the data directory"); + } + if (!isset($hostname_access_error)) { + $bname=basename($filename); + $cleanfilename = clean_filename($bname); + if ($cleanfilename) { + $newfile = "$basedir/$cleanfilename"; + if (move_uploaded_file($filename, $newfile)) { + chmod($newfile, 0666); + } else { + notify(get_string("uploadproblem", "", $filename)); + } + } + $filename = $newfile; + } + print_heading_with_help($strimportquestions, "import", "quiz"); + print_simple_box_start("center", "", "$THEME->cellheading"); + if ($hostname_access_error) { notify("couldn't connect to ODBC Socket Server on " . $hostname_access_error); } + echo "
"; + echo ""; + + echo ""; + echo ""; + echo ""; + echo ""; + echo "
"; + echo "What is the hostname or IP address of the ODBC Socket Server:"; + echo " "; + echo " "; + echo " id."\">"; + echo " format."\">"; + echo " 
"; + echo "What is the location of the database (.mdb file) on the Socket Server:"; + echo " "; + echo " 
"; + echo "What is the location of the system database (System.mda file) on the Socket Server:"; + echo " "; + echo " 
 "; + echo " "; + echo "
"; + echo "
"; + print_simple_box_end(); + print_footer($course); + exit; + } + + // we get here if running windows or after connect to ODBC socket server on linux +// +// this generates the page to choose categories of questions to import +// + if (!isset($question_category)) { + + if (PHP_OS == "WINNT") { + // copy the file to a semi-permanent location + if (! $basedir = make_upload_directory("$course->id")) { + error("The site administrator needs to fix the file permissions for the data directory"); + } + $bname=basename($filename); + $cleanfilename = clean_filename($bname); + if ($cleanfilename) { + $newfile = "$basedir/$cleanfilename"; + if (move_uploaded_file($filename, $newfile)) { + chmod($newfile, 0666); + } else { + notify(get_string("uploadproblem", "", $filename)); + } + } + $filename = $newfile; + } + // end of file copy + + // don't have to do this on linux, since it's alreay been done in the test above + if (PHP_OS == "WINNT") { $question_categories = $this->getquestioncategories($filename); } + // print the intermediary form + if (!$categories = quiz_get_category_menu($course->id, true)) { + error("No categories!"); + } + print_heading_with_help($strimportquestions, "import", "quiz"); + print_simple_box_start("center", "", "$THEME->cellheading"); + echo "
"; + echo ""; + echo ""; + echo ""; + echo "
"; + echo "Choose a category of questions to import:"; + asort($question_categories); + choose_from_menu($question_categories, "question_category","All Categories","All Categories", "", "allcategories"); + echo " "; + echo " id."\">"; + echo " format."\">"; + if (PHP_OS == "Linux") { + echo " "; + echo " "; + echo " "; + } + echo " 
 "; + echo " "; + echo "
"; + echo "
"; + print_simple_box_end(); + print_footer($course); + exit; + } +// +// this is the main import section +// + notify("Importing questions"); + if (PHP_OS == "Linux") { + $hostname = trim($hostname); + $records = $this->getquestions($mdbpath,$question_category,$mdapath, $hostname); + } else { + $records = $this->getquestions($filename,$question_category); + } + foreach ($records as $qrec) + { + $question = NULL; + $question->image = ""; // No images with this format + if ($qrec[9] != "") { + $question->image = $qrec[9]; + } + $question->defaultgrade = 1; +// 0 Selected +// 1 PracticeTestOK? +// 2 QuestionText +// 3 QuestionType +// 4 Option1Text +// 5 Option2Text +// 6 Option3Text +// 7 Option4Text +// 8 CorrectAnswer +// 9 Graphic +// 10 Module +// 11 ChapterNumber +// 12 PageNumber + $ref = "Answer can be found in chapter ". $qrec[11] . ", page " . $qrec[12] . "."; + switch ($qrec[3]) { + case 1: + $question->qtype = MULTICHOICE; // MULTICHOICE, SHORTANSWER, TRUEFALSE + // echo "
";echo htmlspecialchars($qrec[2]); echo "
"; + $question->questiontext = addslashes(trim($qrec[2])); + // echo "
";echo $question->questiontext; echo "
"; + $question->name = preg_replace("/
/", "", $question->questiontext); + $question->single = 1; // Only one answer is allowed -- used for multiple choicers + $fractionset = 0; + for ($i=4;$i<=7;$i++) { + if ($qrec[$i] != "") { + $question->answer[$i-3]=addslashes($qrec[$i]); + if ($qrec[8] == $i-3) { // if this is the index of CorrectAnswer + $question->fraction[$i-3] = 1; + $fractionset = 1; + } else { + $question->fraction[$i-3] = 0; + } + $question->feedback[$i-3] = (($qrec[8] == $i-3)?"Correct. ":"Incorrect. ") . $ref; + } + } + if ($fractionset == 0) { $question->fraction[1] = 1; } + break; + case 2: // TRUE FALSE + $question->qtype = TRUEFALSE; + $question->questiontext = addslashes(trim($qrec[2])); + $question->name = preg_replace("/
/", "", $question->questiontext); + // for TF, $question->answer should be 1 for true, 0 for false + if ($qrec[8] == "T") { $question->answer =1;} else { $question->answer = 0; } + // for TF, use $question->feedbacktrue and feedbackfalse + $question->feedbacktrue = (($qrec[8] =="T")?"Correct. ":"Incorrect. ") . $ref; + $question->feedbackfalse = (($qrec[8] =="F")?"Correct. ":"Incorrect. ") . $ref; + break; + case 3: + $question->qtype = SHORTANSWER; + $question->questiontext = addslashes(trim($qrec[2])); + // echo "
";echo $question->questiontext; echo "
"; + $question->name = preg_replace("/
/", "", $question->questiontext); + $question->usecase=0; // Ignore case -- for SHORT ANSWER questions + $answers = explode("~", $qrec[8]); + $question->answer[0]=" "; + $question->fraction[0]=1; + for ($i=0;$ianswer[$i] = addslashes(trim($answers[$i])); + $question->feedback[$i] = $ref; + $question->fraction[$i] = 1; // 1 for 100%, 0 for none or somewhere in between + } + break; + case 4: + $question = 0; + notify("Cannot use essay questions - skipping question ". $qrec[2] . " " . $ref); + break; + default: + $question = 0; + notify("Misformatted Record. Question Skipped."); + break; + } + if ($question) { $questions[] = $question; } + } + $count = 0; + // process all the questions + if (PHP_OS == "WINNT") { + $filename = str_replace("\\\\","\\",$filename); + $filename = str_replace("/","\\",$filename); + } + foreach ($questions as $question) { // Process and store each question + $count++; + echo "

$count. ".stripslashes($question->questiontext)."

"; + $question->category = $this->category->id; + $question->stamp = make_unique_id_code(); // Set the unique code (not to be changed) + $question->version = 1; // Original version of this question + if (!$question->id = insert_record("quiz_questions", $question)) { + error("Could not insert new question!"); + } + $this->questionids[] = $question->id; + // Now to save all the answers and type-specific options + $result = quiz_save_question_options($question); + if (!empty($result->error)) { + notify($result->error); + $this->deletedatabase($filename); + return false; + } + if (!empty($result->notice)) { + notify($result->notice); + $this->deletedatabase($filename); + return true; + } + } + $this->deletedatabase($filename); + return true; + } + + function importpostprocess() { + return true; + } + + function deletedatabase($filename) { + if (! $this->fulldelete($filename)) { + echo "
Error: Could not delete: $filename"; + return false; + } + return true; + } + + function getquestions($filename, $category, $mdapath="", $hostname="") { + if (($category == "allcategories") or ($category == "")) { + $sql = "SELECT * FROM TBQuestions"; + } else { + $sql = "SELECT * FROM TBQuestions where module = '".$category."'"; + } + if (PHP_OS == "WINNT") { + $ldb =& $this->connect_win($filename); + $qset = $ldb->Execute("$sql"); + if ( $qset->RecordCount() > 0 ) { + $records = $qset->GetAssoc(true); + } else { + $this->err("There were no records in the database.",$dsn); + $ldb->Close(); + return false; + } + $ldb->Close(); + } else { // if PHP_OS == WINNT + // we have a linux installation + $result = $this->query_linux($sql,$filename, $mdapath,$hostname); + if ( count($result) > 0 ) { + // get rid of the ID field in the first column. + for($i=0;$ierr("There were no records in the database.",$dsn); + $ldb->Close(); + return false; + } + // xml test and connect + } // PHP_OS TEST + return $records; + } + + function getquestioncategories($filename, $mdapath="", $hostname="") { + global $CFG, $result; + $sql = "SELECT Distinct module FROM TBQuestions"; + if (PHP_OS == "WINNT") { + $ldb =& $this->connect_win($filename); + $qset = $ldb->Execute("$sql"); + if ( $qset->RecordCount() > 0 ) { + $records = $qset->GetArray(true); + foreach ($records as $record) { + $categories[$record[0]] = $record[0]; + } + } else { // if recordcount + $this->err("There were no records in the database.",$dsn); + $ldb->Close(); + return false; + } + $ldb->Close(); + } else { // if PHP_OS == WINNT + // we have a linux installation + $result = $this->query_linux($sql, $filename, $mdapath, $hostname); + for($i=0;$isHostName = $hostname; + $oTest->nPort = 9628; +// $oTest->sConnectionString="DRIVER=Microsoft Access Driver (*.mdb);SystemDB=C:\CTM\System.mda;DBQ=C:\CTM\of2K3\ctm.mdb;UID=Assess;PWD=VBMango;"; + $oTest->sConnectionString="DRIVER=Microsoft Access Driver (*.mdb);SystemDB=". + $mdapath.";DBQ=".$mdbpath.";UID=Assess;PWD=VBMango;"; + // send and receive XML communication + $qResult = $oTest->ExecSQL($sql); + // set up XML parser to read the results + $xml_parser = xml_parser_create("US-ASCII"); + xml_set_element_handler($xml_parser, "quiz_xmlstart", "quiz_xmlend"); + xml_set_character_data_handler($xml_parser, "quiz_xmldata"); + // parse the XML and get back the result set array + if (!xml_parse($xml_parser, $qResult)) { + $this->err("XML error: ".xml_error_string(xml_get_error_code($xml_parser)) + ." at line ".xml_get_current_line_number($xml_parser),$oTest->sConnectionString); + return false; + } else { +// echo("Successful XML parse. "); + // prepare the array for use in the pull-down +/* echo "
count of rows is ". count ($result); + echo "
\n";
+					$qResult = HtmlSpecialChars($qResult);
+					echo $qResult;
+					echo "\n
"; +*/ + xml_parser_free($xml_parser); +// $sResult = HtmlSpecialChars($qResult); + //echo("
");
+//	echo($sResult);
+//	echo("
"); + + return $result; + } + } + + function connect_win($filename) { + global $CFG, $systemdb; + // first, verify the location of System.mda + if (!isset($systemdb)) { + $systemdb=$this->findfile("System.mda"); + } + if (! $systemdb) { + $this->err("The system database System.mda cannot be found. Check that you've uploaded it to the course.",$dsn); + die; + } + + $ldb = &ADONewConnection('access'); + $dsn="DRIVER=Microsoft Access Driver (*.mdb);SystemDB=".$systemdb.";DBQ=".$filename.";UID=Assess;PWD=VBMango;"; + $dbconnected = $ldb->Connect($dsn); + if (! $dbconnected) { + $this->err("Moodle could not connect to the database.",$dsn); + die; + } + return $ldb; + } + + function err($message, $dsn) { + echo ""; + echo "

Error: $message

"; + echo "

ODBC File DSN: $dsn
"; + echo "
"; + } + + function fulldelete($location) { + if (is_dir($location)) { + $currdir = opendir($location); + while ($file = readdir($currdir)) { + if ($file <> ".." && $file <> ".") { + $fullfile = $location."/".$file; + if (is_dir($fullfile)) { + if (!fulldelete($fullfile)) { + return false; + } + } else { + if (!unlink($fullfile)) { + return false; + } + } + } + } + closedir($currdir); + if (! rmdir($location)) { + return false; + } + + } else { + if (!unlink($location)) { + return false; + } + } + return true; + } + + + function findfile($filename) { + global $CFG; + $dirs = $this->getcoursedirs(); + $dirs[] = $CFG->dirroot."\mod\quiz\format"; + foreach ($dirs as $dir) { + $file = $dir . "\System.mda"; + // look for System.mda + if (is_file($file)) return $file; + } + return false; + } + + function getcoursedirs() { + global $CFG; + // for every course in the system, find the root of the data directory + $courses = get_records_sql("select distinct id,fullname from ".$CFG->prefix."course"); + $dirs = array(); + if ($courses) { + foreach ($courses as $course) { + $dir = $CFG->dataroot . "/" . $course->id; + if (is_dir($dir)) { $dirs[] = $dir; } + } + } + return $dirs; + } + +} // END OF CLASS + + //Handler for starting elements + function quiz_xmlstart($parser, $name, $attribs) { + global $result,$row, $col, $incolumn; + $name = strtolower($name); + switch ($name) { + case "row": + $col=0;break; + case "column": + $incolumn = 1;break; + case "error": + break; + case "result": + $row = 0; break; + } // switch + } + + //handler for the end of elements + function quiz_xmlend($parser, $name) { + global $result, $row, $col, $incolumn; + $name = strtolower($name); + switch ($name) { + case "row": + $row++;break; + case "column": + $incolumn = 0; + $col++; + break; + case "error": + break; + case "result": + break; + } // switch + } // function + + //handler for character data + function quiz_xmldata($parser, $data) { + global $result, $row, $col, $incolumn; + if ($incolumn) { $result[$row][$col] = $result[$row][$col] . $data; + } + } + +?> diff --git a/mod/quiz/format/custom.php b/mod/quiz/format/custom.php deleted file mode 100644 index 05ed991b15..0000000000 --- a/mod/quiz/format/custom.php +++ /dev/null @@ -1,25 +0,0 @@ - diff --git a/mod/quiz/format/gift/format.php b/mod/quiz/format/gift/format.php new file mode 100755 index 0000000000..3a5065fb29 --- /dev/null +++ b/mod/quiz/format/gift/format.php @@ -0,0 +1,274 @@ + 0 and $answer_weight <> 1){ + /// $question->single = 0; // ok many good answers + /// } + return $answer_weight; + } + + + function commentparser(&$answer) { + //Answer Comment parser + if (strpos($answer,"#") > 0){ + $hashpos = strpos($answer,"#"); + $comment = addslashes(substr($answer, $hashpos+1)); + $answer = substr($answer, 0, $hashpos); + } else { + $comment = " "; + } + return $comment; + } + + + function readquestion($lines) { + /// Given an array of lines known to define a question in this format, this function + /// converts it into a question object suitable for processing and insertion into Moodle. + + $question = NULL; + $comment = NULL; + $answer_weight_regex = "^%\-*([0-9]{1,2})\.?([0-9]*)%"; + $text = implode(" ", $lines); + + /// QUESTION NAME parser + $text = trim($text); + if (substr($text, 0, 2) == "::") { + $text = substr($text, 2); + + $namefinish = strpos($text, "::"); + if ($namefinish === false) { + $question->name = false; + // name will be assigned after processing question text below + } else { + $question->name = addslashes(trim(substr($text, 0, $namefinish))); + $text = substr($text, $namefinish+2); // Remove name from text + } + } else { + $question->name = false; + } + + + /// FIND ANSWER section + $answerstart = strpos($text, "{"); + if ($answerstart === false) { + if ($this->displayerrors) { + echo "

$text

Could not find a {"; + } + return false; + } + + $answerfinish = strpos($text, "}"); + if ($answerfinish === false) { + if ($this->displayerrors) { + echo "

$text

Could not find a }"; + } + return false; + } + + $answerlength = $answerfinish - $answerstart; + $answertext = trim(substr($text, $answerstart + 1, $answerlength - 1)); + + /// SAVE QUESTION TEXT without answer, inserting "_____" as necessary + if (substr($text, -1) == "}") { + /// no blank line if answers follow question, outside of closing punctuation + $question->questiontext = addslashes(substr_replace($text, "", $answerstart, $answerlength+1)); + } else { + /// inserts blank line for missing word format + $question->questiontext = addslashes(substr_replace($text, "_____", $answerstart, $answerlength+1)); + } + + /// set question name if not already set + if ($question->name === false) { + $question->name = $question->questiontext; + } + + + /// ANSWERS + // Only Multiple-choice questions contain tilde ~ + if (strstr($answertext, "~") !== FALSE) { // tilde conditional + + ///MULTIPLE CHOICE + $answertext = str_replace("=", "~=", $answertext); + $answers = explode("~", $answertext); + if (isset($answers[0])) { + $answers[0] = trim($answers[0]); + } + if (empty($answers[0])) { + array_shift($answers); + } + + $countanswers = count($answers); + if ($countanswers < 2) { // MC $countanswers conditional + if ($this->displayerrors) { + echo "

Found tilde, but " . $countanswers . " answers in $answertext"; + } + return false; + } else { // MC $countanswers conditional + + $question->qtype = MULTICHOICE; + $question->single = 1; // Only one answer allowed by default + + foreach ($answers as $key => $answer) { + $answer = trim($answer); + + // determine answer weight + if ($answer[0] == "=") { + $answer_weight = 1; + $answer = substr($answer, 1); + + } elseif (ereg($answer_weight_regex, $answer)) { // check for properly formatted answer weight + $answer_weight = $this->answerweightparser($answer); + + } else { //default, i.e., wrong anwer + $answer_weight = 0; + } + $question->fraction[$key] = $answer_weight; + + $comment = $this->commentparser($answer); // commentparser also cleans up $answer + $question->feedback[$key] = $comment; + + $question->answer[$key] = addslashes($answer); + } // end foreach answer + + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + return $question; + } // end MC $countanswers conditional + + /// Otherwise, begin parsing other question-types + } else { // tilde conditional + + /// TRUEFALSE Question + $TF_check = $answertext; + if (strpos($answertext,"#") > 0){ + // strip comments to check for TrueFalse question + $TF_check = trim(substr($answertext, 0, strpos($answertext,"#"))); + } + + if (($TF_check == "T") // TrueFalse/ShortAnswer QuestionType conditional + OR ($TF_check == "TRUE") + OR ($TF_check == "F") + OR ($TF_check == "FALSE")) { + $answer = $answertext; + $question->qtype = TRUEFALSE; + $comment = $this->commentparser($answer); // commentparser also cleans up $answer + + if ($answer == "T" OR $answer == "TRUE") { + $question->answer = 1; + $question->feedbackfalse = $comment; //feedback if answer is wrong + } else { + $question->answer = 0; + $question->feedbacktrue = $comment; //feedback if answer is wrong + } + + + } else { // TrueFalse/ShortAnswer QuestionType conditional + + /// SHORTANSWER Question + $answers = explode("=", $answertext); + if (isset($answers[0])) { + $answers[0] = trim($answers[0]); + } + if (empty($answers[0])) { + array_shift($answers); + } + + if (count($answers) == 0) { + /// invalid question + if ($this->displayerrors) { + echo "

Found equals=, but no answers in $answertext"; + } + return false; + } else { + + $question->qtype = SHORTANSWER; + $question->usecase = 0; // Ignore case + + foreach ($answers as $key => $answer) { + $answer = trim($answer); + + // Answer Weight + if (ereg($answer_weight_regex, $answer)) { // check for properly formatted answer weight + $answer_weight = $this->answerweightparser($answer); + } else { //default, i.e., full-credit anwer + $answer_weight = 1; + } + $question->fraction[$key] = $answer_weight; + + $comment = $this->commentparser($answer); //commentparser also cleans up $answer + $question->feedback[$key] = $comment; + + $question->answer[$key] = addslashes($answer); + } // end foreach + } // end ount($answers) conditional + + } // end TrueFalse/ShortAnswer QuestionType conditional + + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + return $question; + + } // end tilde conditional + + } // end function readquestion($lines) +} + +?> + diff --git a/mod/quiz/format/missingword/format.php b/mod/quiz/format/missingword/format.php new file mode 100644 index 0000000000..7014b72bf7 --- /dev/null +++ b/mod/quiz/format/missingword/format.php @@ -0,0 +1,156 @@ +displayerrors) { + echo "

$text

Could not find a {"; + } + return false; + } + + $answerfinish = strpos($text, "}"); + if ($answerfinish === false) { + if ($this->displayerrors) { + echo "

$text

Could not find a }"; + } + return false; + } + + $answerlength = $answerfinish - $answerstart; + $answertext = substr($text, $answerstart + 1, $answerlength - 1); + + /// Save the new question text + $question->questiontext = addslashes(substr_replace($text, "_____", $answerstart, $answerlength+1)); + $question->name = $question->questiontext; + + + /// Parse the answers + $answertext = str_replace("=", "~=", $answertext); + $answers = explode("~", $answertext); + if (isset($answers[0])) { + $answers[0] = trim($answers[0]); + } + if (empty($answers[0])) { + array_shift($answers); + } + + $countanswers = count($answers); + + switch ($countanswers) { + case 0: // invalid question + if ($this->displayerrors) { + echo "

No answers found in $answertext"; + } + return false; + + case 1: + $question->qtype = SHORTANSWER; + + $answer = trim($answers[0]); + if ($answer[0] == "=") { + $answer = substr($answer, 1); + } + $question->answer[] = addslashes($answer); + $question->fraction[] = 1; + $question->feedback[] = ""; + + $question->usecase = 0; // Ignore case + $question->defaultgrade = 1; + $question->image = ""; // No images with this format + return $question; + + default: + $question->qtype = MULTICHOICE; + + foreach ($answers as $key => $answer) { + $answer = trim($answer); + + // Tom's addition starts here + $answeight = 0; + if (strspn($answer,"1234567890%") > 0){ + //Make sure that the percent sign is the last in the span + if (strpos($answer,"%") == strspn($answer,"1234567890%") - 1) { + $answeight0 = substr($answer,0,strspn($answer,"1234567890%")); + $answeight = round(($answeight0/100),2); + $answer = substr($answer,(strspn($answer,"1234567890%"))); + } + } + if ($answer[0] == "="){ + $answeight = 1; + } + //remove the protective underscore for leading numbers in answers + if ($answer[0] == "_"){ + $answer = substr($answer, 1); + } + $answer = trim($answer); + + if (strpos($answer,"#") > 0){ + $hashpos = strpos($answer,"#"); + $comment = addslashes(substr(($answer),$hashpos+1)); + $answer = substr($answer,0,$hashpos); + } else { + $comment = " "; + } + // End of Tom's addition + + if ($answer[0] == "=") { +# $question->fraction[$key] = 1; + $question->fraction[$key] = $answeight; + $answer = substr($answer, 1); + } else { +# $question->fraction[$key] = 0; + $question->fraction[$key] = $answeight; + } + $question->answer[$key] = addslashes($answer); + $question->feedback[$key] = $comment; + } + + $question->defaultgrade = 1; + $question->single = 1; // Only one answer is allowed + $question->image = ""; // No images with this format + return $question; + } + } +} + +?> diff --git a/mod/quiz/format/multianswer/format.php b/mod/quiz/format/multianswer/format.php new file mode 100644 index 0000000000..a7fd72e1f9 --- /dev/null +++ b/mod/quiz/format/multianswer/format.php @@ -0,0 +1,162 @@ +qtype= MULTIANSWER; + $question->questiontext= $text; + $question->answers= array(); + $question->defaultgrade = 0; // Will be increased for each answer norm + + for ($positionkey=1 + ; ereg(ANSWER_REGEX, $question->questiontext, $answerregs) + ; ++$positionkey ) + { + unset($multianswer); + + $multianswer->positionkey = $positionkey; + $multianswer->norm = $answerregs[ANSWER_REGEX_NORM] + or $multianswer->norm = '1'; + if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL]) { + $multianswer->answertype = NUMERICAL; + } else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER]) { + $multianswer->answertype = SHORTANSWER; + } else if($answerregs[ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE]){ + $multianswer->answertype = MULTICHOICE; + } else { + error("Cannot identify answertype $answerregs[2]"); + return false; + } + + $multianswer->alternatives= array(); + $remainingalts = $answerregs[ANSWER_REGEX_ALTERNATIVES]; + while (ereg(ANSWER_ALTERNATIVE_REGEX, $remainingalts, $altregs)) { + unset($alternative); + + if ('=' == $altregs[ANSWER_ALTERNATIVE_REGEX_FRACTION]) { + $alternative->fraction = '1'; + } else { + $alternative->fraction = .01 * + $altregs[ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION] + or $alternative->fraction = '0'; + } + $alternative->feedback = $altregs[ANSWER_ALTERNATIVE_REGEX_FEEDBACK]; + if ($answerregs[ANSWER_REGEX_ANSWER_TYPE_NUMERICAL] + && ereg(NUMERICAL_ALTERNATIVE_REGEX, + $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER], + $numregs) ) + { + $alternative->answer = $numregs[NUMERICAL_CORRECT_ANSWER]; + if ($numregs[NUMERICAL_ABS_ERROR_MARGIN]) { + $alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER] + - $numregs[NUMERICAL_ABS_ERROR_MARGIN]; + $alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER] + + $numregs[NUMERICAL_ABS_ERROR_MARGIN]; + } else { + $alternative->min = $numregs[NUMERICAL_CORRECT_ANSWER]; + $alternative->max = $numregs[NUMERICAL_CORRECT_ANSWER]; + } + } else { // Min and max must stay undefined... + $alternative->answer = + $altregs[ANSWER_ALTERNATIVE_REGEX_ANSWER]; + } + + $multianswer->alternatives[] = $alternative; + $tmp = explode($altregs[0], $remainingalts, 2); + $remainingalts = $tmp[1]; + } + + $question->defaultgrade += $multianswer->norm; + $question->answers[] = $multianswer; + $question->questiontext = implode("{#$positionkey}", + explode($answerregs[0], $question->questiontext, 2)); + } + return $question; +} + +class quiz_file_format extends quiz_default_format { + + function readquestions($lines) { + /// Parses an array of lines into an array of questions. + /// For this class the method has been simplified as + /// there can never be more than one question for a + /// multianswer import + + $questions= array(); + $thequestion= extractMultiAnswerQuestion(implode('',$lines)); + + if (!empty($thequestion)) { + $thequestion->name = $lines[0]; + + $questions[] = $thequestion; + } + + return $questions; + } +} + +?> diff --git a/mod/quiz/format/qti/format.php b/mod/quiz/format/qti/format.php new file mode 100644 index 0000000000..9c0bab3533 --- /dev/null +++ b/mod/quiz/format/qti/format.php @@ -0,0 +1,19 @@ +wwwroot/mod/quiz/import.php?category=$category->id"); + } + +} + +?> diff --git a/mod/quiz/format/webct/format.php b/mod/quiz/format/webct/format.php new file mode 100644 index 0000000000..1b8ded9dc1 --- /dev/null +++ b/mod/quiz/format/webct/format.php @@ -0,0 +1,19 @@ +wwwroot/mod/quiz/import.php?category=$category->id"); + } + +} + +?> diff --git a/mod/quiz/import.php b/mod/quiz/import.php index 8259a9c644..9219963cd8 100644 --- a/mod/quiz/import.php +++ b/mod/quiz/import.php @@ -49,12 +49,12 @@ } else { // Valid file is found - if (! is_readable("format/$form->format".".php")) { + if (! is_readable("format/$form->format/format.php")) { error("Format not known ($form->format)"); } - require("format/default.php"); // Parent class - require("format/$form->format".".php"); + require("format.php"); // Parent class + require("format/$form->format/format.php"); $format = new quiz_file_format(); @@ -83,6 +83,18 @@ error("No categories!"); } + $fileformats = get_list_of_plugins("mod/quiz/format"); + $fileformatname = array(); + foreach ($fileformats as $key => $fileformat) { + $formatname = get_string($fileformat, 'quiz'); + if ($formatname == "[[$fileformat]]") { + $formatname = $fileformat; // Just use the raw folder name + } + $fileformatnames[$fileformat] = $formatname; + } + natcasesort($fileformatnames); + + print_heading_with_help($strimportquestions, "import", "quiz"); print_simple_box_start("center", "", "$THEME->cellheading"); @@ -92,14 +104,13 @@ echo ""; print_string("category", "quiz"); echo ":"; - asort($QUIZ_FILE_FORMAT); choose_from_menu($categories, "category", "$category->id", ""); echo ""; echo ""; print_string("fileformat", "quiz"); echo ":"; - choose_from_menu($QUIZ_FILE_FORMAT, "format", "custom", ""); + choose_from_menu($fileformatnames, "format", "gift", ""); helpbutton("import", $strimportquestions, "quiz"); echo ""; diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index dc45f5462c..3238ac6532 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -34,14 +34,6 @@ $QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz") MULTIANSWER => get_string("multianswer", "quiz") ); -$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"), - "missingword" => get_string("missingword", "quiz"), - "blackboard" => get_string("blackboard", "quiz"), - "aon" => "AON", - "aiken" => "Aiken", - "multianswer" => get_string("multianswer", "quiz"), - "coursetestmanager" => "Course Test Manager" - ); define("QUIZ_PICTURE_MAX_HEIGHT", "600"); // Not currently implemented define("QUIZ_PICTURE_MAX_WIDTH", "600"); // Not currently implemented -- 2.39.5