<P ALIGN=CENTER><B>Importing new questions</B></P>
<P>This function allows you to import questions from
- external text files.
+ external text files, uploaded through a form.
-<P>Several common file formats are supported:
+<P>A number of file formats are supported:
-<P><B>Missing word format</B></P>
+<P><B>Missing Word</B></P>
<UL>
<P>This format only supports multiple choice questions.
Each answer is separated with a tilde (~), and the correct answer is
</UL>
-<P><B>IMS QTI format</B></P>
+<P><B>AON</B></P>
<UL>
-<P>The standard format for Question and Test Interoperability, from IMS.
-<P>Not yet implemented
-
-<P>More info: <? helpbutton("formatqti", "", "quiz") ?></P>
+<P>This is the same as Missing Word Format, except that after importing
+ the questions all Short-Answer questions are converted four at a time
+ into Matching Questions.</P>
+<p>It's named after an organisation that sponsored the development of many
+ quiz features</p>
</UL>
-<P><B>WebCT format</B></P>
+<P><B>Blackboard</B></P>
<UL>
-<P>The format of files exported from WebCT.
-<P>Not yet implemented
+<P>This module can import questions saved in Blackboard's export
+format. It relies on XML functions being compiled into your PHP.</P>
-<P>More info: <? helpbutton("formatwebct", "", "quiz") ?></P>
+<P>More info: <? helpbutton("formatblackboard", "", "quiz") ?></P>
</UL>
-<P><B>Custom format</B></P>
+<P><B>Custom</B></P>
<UL>
<P>If you have your own format that you need to import, you can
implement it yourself by editing mod/quiz/format/custom.php
<P>More info: <? helpbutton("formatcustom", "", "quiz") ?></P>
</UL>
+
+
+<P>More formats are yet to come, including WebCT, IMS QTI and whatever else
+ Moodle users can contribute! </p>
$string['attemptsunlimited'] = "Unlimited attempts";
$string['backtoquiz'] = "Back to quiz editing";
$string['bestgrade'] = "Best grade";
+$string['blackboard'] = "Blackboard";
$string['casesensitive'] = "Case sensitivity";
$string['caseyes'] = "Yes, case must match";
$string['caseno'] = "No, case is unimportant";
}
}
- function postprocess($category, $questionids) {
+ function postprocess() {
/// 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 $db, $CFG;
+ global $CFG;
- print_heading(count($questionids)." ".get_string("questions", "quiz"));
+ print_heading(count($this->questionids)." ".get_string("questions", "quiz"));
- $questionids = implode(',', $questionids);
+ $questionids = implode(',', $this->questionids);
if (!$shortanswers = get_records_select("quiz_questions",
"id IN ($questionids) AND qtype = ".SHORTANSWER,
$shortanswerids[] = $key;
}
- $strmatch = "$category->name - ".get_string("match", "quiz");
+ $strmatch = get_string("match", "quiz")." (".$this->category->name.")";
$shortanswerids = swapshuffle($shortanswerids);
$count = $shortanswercount = count($shortanswerids);
$i = 1;
$matchcount = 0;
- $question->category = $category->id;
+ $question->category = $this->category->id;
$question->qtype = MATCH;
$question->questiontext = get_string("randomsamatchintro", "quiz");
$question->image = "";
print_heading($info);
- $options['category'] = $category->id;
- echo "<CENTER>";
+ $options['category'] = $this->category->id;
+ echo "<center>";
print_single_button("multiple.php", $options, get_string("randomcreate", "quiz"));
- echo "</CENTER>";
+ echo "</center>";
return true;
}
--- /dev/null
+<?PHP // $Id$
+////////////////////////////////////////////////////////////////////////////
+/// Blackboard 6.0 Format
+///
+/// This Moodle class provides all functions necessary to import and export
+///
+///
+////////////////////////////////////////////////////////////////////////////
+require ("default.php");
+require_once ("$CFG->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"];
+
+ $question->feedbacktrue = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_CORRECT"][0]["#"]));
+ $question->feedbackfalse = addslashes(trim($thisquestion["#"]["GRADABLE"][0]["#"]["FEEDBACK_WHEN_INCORRECT"][0]["#"]));
+
+ if (strcmp($id, $correct_answer) == 0) // true is correct
+ $question->answer = 1;
+ else // false is correct
+ $question->answer = 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;
+
+ }
+}
+?>
+++ /dev/null
-<?PHP // $Id$
-
-////////////////////////////////////////////////////////////////////////////
-/// CUSTOM FORMAT
-///
-/// This format provides a starting point for creating your own
-/// import format.
-///
-/// If your questions are separated by blank lines, then you will
-/// only need to modify the readquestion() function to parse the
-/// lines into each question. See default.php for the other
-/// functions that you may need to override if you have different
-/// needs.
-///
-/// See missingword.php for an example of how it's done, and see
-/// the top of ../lib.php for some constants you might need.
-///
-////////////////////////////////////////////////////////////////////////////
-
-require("default.php");
-
-class quiz_file_format extends quiz_default_format {
-
- function readquestions($lines) {
- /// Parses an array of lines into an array of questions,
- /// where each item is a question object as defined by
- /// readquestion(). Questions are defined as anything
- /// between blank lines.
-
- $questions = array();
- $currentquestion = array();
-
- foreach ($lines as $line) {
- $line = trim($line);
- if (empty($line)) {
- if (!empty($currentquestion)) {
- if ($question = $this->readquestion($currentquestion)) {
- $questions[] = $question;
- }
- $currentquestion = array();
- }
- } else {
- $currentquestion[] = $line;
- }
- }
-
- return $questions;
- }
-
-
-
- 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;
-
- return $question;
- }
-}
-
-?>
/// This format provides a starting point for creating your own
/// import format.
///
-/// If your questions are separated by blank lines, then you will
-/// only need to modify the readquestion() function to parse the
-/// lines into each question. See default.php for the other
-/// functions that you may need to override if you have different
-/// needs.
+/// See default.php for the functions that you may need to override
+/// if you have different needs.
///
/// See missingword.php for an example of how it's done, and see
/// the top of ../lib.php for some constants you might need.
class quiz_file_format extends quiz_default_format {
- function readquestions($lines) {
- /// Parses an array of lines into an array of questions,
- /// where each item is a question object as defined by
- /// readquestion(). Questions are defined as anything
- /// between blank lines.
-
- $questions = array();
- $currentquestion = array();
- foreach ($lines as $line) {
- $line = trim($line);
- if (empty($line)) {
- if (!empty($currentquestion)) {
- if ($question = $this->readquestion($currentquestion)) {
- $questions[] = $question;
- }
- $currentquestion = array();
- }
- } else {
- $currentquestion[] = $line;
- }
- }
- return $questions;
- }
-
-
-
- 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;
-
- return $question;
- }
}
?>
class quiz_default_format {
var $displayerrors = true;
+ var $category = NULL;
+ var $questionids = array();
/// Importing functions
function preprocess($category) {
/// Does any pre-processing that may be desired
+ $this->category = $category; // Important
+
return true;
}
+ function process($filename) {
+ /// Processes a given file. There's probably little need to change this
+
+ if (! $lines = $this->readdata($filename)) {
+ notify("File could not be read, or was empty");
+ return false;
+ }
+
+ if (! $questions = $this->readquestions($lines)) { // Extract all the questions
+ notify("There are no questions in this file!");
+ return false;
+ }
+
+ notify("Importing ".count($questions)." questions");
+
+ $count = 0;
+
+ foreach ($questions as $question) { // Process and store each question
+ $count++;
+
+ echo "<hr><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>";
+
+ $question->category = $this->category->id;
+
+ 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);
+ return false;
+ }
+
+ if (!empty($result->notice)) {
+ notify($result->notice);
+ return true;
+ }
+ }
+ return true;
+ }
+
+
function readdata($filename) {
/// Returns complete file with an array, one item per line
/// this format, this function converts it into a question
/// object suitable for processing and insertion into Moodle.
- echo "<p>You need to override the readquestion() function";
+ echo "<p>This quiz format has not yet been completed!</p>";
return NULL;
}
- function postprocess($questionids) {
+ function postprocess() {
/// Does any post-processing that may be desired
/// Argument is a simple array of question ids that
/// have just been added.
}
}
- function postprocess($questionids) {
- /// Does any post-processing that may be desired
- /// Argument is a simple array of question ids that
- /// have just been added.
-
- return true;
- }
-
}
?>
////////////////////////////////////////////////////////////////////////////
/// IMS QTI FORMAT
-///
-/// This Moodle class provides all functions necessary to import and export
-/// QTI-formatted XML question files.
-///
////////////////////////////////////////////////////////////////////////////
require("default.php");
class quiz_file_format extends quiz_default_format {
- function readquestions($lines) {
- /// Parses an array of lines into an array of questions,
- /// where each item is a question object as defined by
- /// readquestion().
-
- $questions = array();
- /// FIXME
-
- return $questions;
- }
-
-
- 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;
-
- /// FIXME
-
- return $question;
- }
}
////////////////////////////////////////////////////////////////////////////
/// WEBCT FORMAT
-///
-/// This Moodle class provides all functions necessary to import and export
-/// WebCT-formatted question files.
-///
////////////////////////////////////////////////////////////////////////////
require("default.php");
class quiz_file_format extends quiz_default_format {
- function readquestions($lines) {
- /// Parses an array of lines into an array of questions,
- /// where each item is a question object as defined by
- /// readquestion().
-
- $questions = array();
- /// FIXME
-
- return $questions;
- }
-
-
- 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;
-
- /// FIXME
-
- return $question;
- }
}
$format = new quiz_file_format();
- if (! $format->preprocess($category)) {
- error("Error occurred during pre-processing.");
+ if (! $format->preprocess($category)) { // Do anything before that we need to
+ error("Error occurred during pre-processing!");
}
- if (! $lines = $format->readdata($newfile['tmp_name'])) {
- error("File could not be read, or was empty");
+ if (! $format->process($newfile['tmp_name'])) { // Process the uploaded file
+ error("Error occurred during processing!");
}
- if (! $questions = $format->readquestions($lines)) {
- error("There are no questions in this file!");
- }
-
- notify("Importing ".count($questions)." questions");
-
- $count = 0;
- $questionids = array();
-
- foreach ($questions as $question) {
- $count++;
-
- echo "<hr><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>";
-
- $question->category = $category->id;
-
- if (!$question->id = insert_record("quiz_questions", $question)) {
- error("Could not insert new question!");
- }
-
- $questionids[] = $question->id;
-
- // Now to save all the answers and type-specific options
-
- $result = quiz_save_question_options($question);
-
- if (!empty($result->error)) {
- error($result->error);
- }
-
- if (!empty($result->notice)) {
- notice($result->notice);
- }
- }
-
- if (! $format->postprocess($category, $questionids)) {
- error("Error occurred during post-processing.");
+ if (! $format->postprocess()) { // In case anything needs to be done after
+ error("Error occurred during post-processing!");
}
echo "<hr>";
print_heading_with_help($strimportquestions, "import", "quiz");
print_simple_box_start("center", "", "$THEME->cellheading");
- echo "<FORM ENCTYPE=\"multipart/form-data\" METHOD=\"POST\" ACTION=import.php>";
- echo "<TABLE cellpadding=5>";
- echo "<TR><TD align=right>";
+ echo "<form enctype=\"multipart/form-data\" method=\"post\" action=import.php>";
+ echo "<table cellpadding=5>";
+
+ echo "<tr><td align=right>";
print_string("category", "quiz");
- echo ":</TD><TD>";
+ echo ":</td><td>";
+ asort($QUIZ_FILE_FORMAT);
choose_from_menu($categories, "category", "$category->id", "");
- echo "</TR>";
+ echo "</tr>";
- echo "<TR><TD align=right>";
+ echo "<tr><td align=right>";
print_string("fileformat", "quiz");
- echo ":</TD><TD>";
+ echo ":</td><td>";
choose_from_menu($QUIZ_FILE_FORMAT, "format", "custom", "");
helpbutton("import", $strimportquestions, "quiz");
- echo "</TR><TR><TD align=right>";
+ echo "</tr>";
+
+ echo "<tr><td align=right>";
print_string("upload");
- echo ":</TD><TD>";
- echo " <INPUT NAME=\"newfile\" TYPE=\"file\" size=\"50\">";
- echo "</TR><TR><TD> </TD><TD>";
- echo " <INPUT TYPE=hidden NAME=category VALUE=\"$category->id\">";
- echo " <INPUT TYPE=submit NAME=save VALUE=\"".get_string("uploadthisfile")."\">";
- echo "</TD></TR>";
- echo "</TABLE>";
- echo "</FORM>";
+ echo ":</td><td>";
+ echo " <input name=\"newfile\" type=\"file\" size=\"50\">";
+ echo "</tr><tr><td> </td><td>";
+ echo " <input type=hidden name=category value=\"$category->id\">";
+ echo " <input type=submit name=save value=\"".get_string("uploadthisfile")."\">";
+ echo "</td></tr>";
+
+ echo "</table>";
+ echo "</form>";
print_simple_box_end();
print_footer($course);
-
?>
RANDOMSAMATCH => get_string("randomsamatch", "quiz") );
$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"),
- "webct" => get_string("webct", "quiz"),
- "qti" => get_string("qti", "quiz"),
- "missingword" => get_string("missingword", "quiz") );
+ "missingword" => get_string("missingword", "quiz"),
+ "blackboard" => get_string("blackboard", "quiz"),
+ "aon" => "AON"
+ );
define("QUIZ_PICTURE_DEFAULT_HEIGHT", "200");