]> git.mjollnir.org Git - moodle.git/commitdiff
Reworking of the plug-in format for import/export files.
authormoodler <moodler>
Fri, 12 Dec 2003 07:35:55 +0000 (07:35 +0000)
committermoodler <moodler>
Fri, 12 Dec 2003 07:35:55 +0000 (07:35 +0000)
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.

15 files changed:
mod/quiz/format.php [moved from mod/quiz/format/default.php with 97% similarity]
mod/quiz/format/README [deleted file]
mod/quiz/format/README.txt [new file with mode: 0644]
mod/quiz/format/aiken/format.php [new file with mode: 0644]
mod/quiz/format/aon/format.php [new file with mode: 0644]
mod/quiz/format/blackboard/format.php [new file with mode: 0644]
mod/quiz/format/coursetestmanager/format.php [new file with mode: 0755]
mod/quiz/format/custom.php [deleted file]
mod/quiz/format/gift/format.php [new file with mode: 0755]
mod/quiz/format/missingword/format.php [new file with mode: 0644]
mod/quiz/format/multianswer/format.php [new file with mode: 0644]
mod/quiz/format/qti/format.php [new file with mode: 0644]
mod/quiz/format/webct/format.php [new file with mode: 0644]
mod/quiz/import.php
mod/quiz/lib.php

similarity index 97%
rename from mod/quiz/format/default.php
rename to mod/quiz/format.php
index 0145c54c7d99987e00cad4041418e8f21448e0ff..2af24e200cafdb7548cb7fd83b3ce4b7ed895fcd 100644 (file)
@@ -1,12 +1,12 @@
 <?PHP  // $Id$ 
 
 ////////////////////////////////////////////////////////////////////
-/// Default class for file imports/exports.                       //
+/// format.php  - Default format class for file imports/exports.  //
 ///                                                               //
 /// Doesn't do everything on it's own -- it needs to be extended. //
 ////////////////////////////////////////////////////////////////////
 
-// Included by ../import.php
+// Included by import.php
 
 class quiz_default_format {
 
diff --git a/mod/quiz/format/README b/mod/quiz/format/README
deleted file mode 100644 (file)
index a850f67..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-This directory contains plug-in sub-modules to add 
-import-export formats to Moodle quizzes.
-
-Each sub-module is a file containing a class that 
-contains functions for reading, writing, importing 
-and exporting quiz questions.
diff --git a/mod/quiz/format/README.txt b/mod/quiz/format/README.txt
new file mode 100644 (file)
index 0000000..0d1590d
--- /dev/null
@@ -0,0 +1,12 @@
+QUIZ FILE FORMATS FOR IMPORT/EXPORT
+------------------------------------
+
+This directory contains plug-in sub-modules to add 
+import-export formats to Moodle quizzes.
+
+Each sub-module must contain at least a format.php file 
+containing a class that contains functions for reading, 
+writing, importing and exporting quiz questions.
+
+Most of them are based on the class found in mod/quiz/format.php
+
diff --git a/mod/quiz/format/aiken/format.php b/mod/quiz/format/aiken/format.php
new file mode 100644 (file)
index 0000000..4c888d4
--- /dev/null
@@ -0,0 +1,91 @@
+<?PHP  // $Id$ 
+
+///
+/// Written by Tom Robb <tom@robb.net> 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<br>");
+                $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 (file)
index 0000000..c9c39af
--- /dev/null
@@ -0,0 +1,224 @@
+<?PHP  // $Id$ 
+
+////////////////////////////////////////////////////////////////////////////
+/// Academy of Nursing format
+///
+/// Based on missingword.php
+///
+/// This Moodle class provides all functions necessary to import and export 
+/// one-correct-answer multiple choice questions in this format:
+///
+///    As soon as we begin to explore our body parts as infants
+///    we become students of {=anatomy and physiology ~reflexology 
+///    ~science ~experiment}, and in a sense we remain students for life.
+/// 
+/// Each answer is separated with a tilde ~, and the correct answer is 
+/// prefixed with an equals sign =
+///
+/// Afterwards, all short-answer questions are randomly packed into 
+/// 4-answer matching questions.
+///
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+class quiz_file_format extends quiz_default_format {
+
+    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;
+
+        $text = implode($lines, " ");
+
+        /// Find answer section
+
+        $answerstart = strpos($text, "{");
+        if ($answerstart === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>Could not find a {";
+            }
+            return false;
+        }
+
+        $answerfinish = strpos($text, "}");
+        if ($answerfinish === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>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 "<P>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 "<center>";
+        print_single_button("multiple.php", $options, get_string("randomcreate", "quiz"));
+        echo "</center>";
+
+        return true;
+    }
+
+}
+
+?>
diff --git a/mod/quiz/format/blackboard/format.php b/mod/quiz/format/blackboard/format.php
new file mode 100644 (file)
index 0000000..66f72ef
--- /dev/null
@@ -0,0 +1,252 @@
+<?PHP // $Id$
+////////////////////////////////////////////////////////////////////////////
+/// Blackboard 6.0 Format
+///
+/// This Moodle class provides all functions necessary to import and export
+///
+///
+////////////////////////////////////////////////////////////////////////////
+
+// Based on default.php, included by ../import.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"];
+
+        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 (executable)
index 0000000..4ba52ef
--- /dev/null
@@ -0,0 +1,507 @@
+<?PHP  // $Id$ 
+////////////////////////////////////////////////////////////////////
+/// Class for importing course test manager questions.            //
+///                                                               //
+///                                                               //
+////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+
+class quiz_file_format {
+    function importpreprocess($category) {
+        $this->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 "<form method=\"post\" action=\"import.php\">";
+                   echo "<table cellpadding=5>";
+
+        echo "<tr><td align=right>";
+                   echo "What is the hostname or IP address of the ODBC Socket Server:</td><td>";
+                   echo " <input name=\"hostname\" type=\"text\" size=\"50\" value=\"".stripslashes($hostname_access_error)."\">";
+                   echo " <input name=\"filename\" type=\"hidden\" value=\"".$filename."\">";
+                   echo " <input name=\"category\" type=\"hidden\" value=\"".$category->id."\">";
+                   echo " <input name=\"format\" type=\"hidden\" value=\"".$form->format."\">";
+                   echo "</td><td>&nbsp;</td></tr>";
+        echo "<tr><td align=right>";
+                   echo "What is the location of the database (.mdb file) on the Socket Server:</td><td>";
+                   echo " <input name=\"mdbpath\" type=\"text\" size=\"50\" value=\"".stripslashes($mdbpath)."\">";
+                               echo "</td><td>&nbsp;</td></tr>";
+        echo "<tr><td align=right>";
+                   echo "What is the location of the system database (System.mda file) on the Socket Server:</td><td>";
+                   echo " <input name=\"mdapath\" type=\"text\" size=\"50\" value=\"".stripslashes($mdapath)."\">";
+                               echo "</td><td>&nbsp;</td></tr>";
+                   echo "<tr><td>&nbsp;</td><td>";
+                   echo " <input type=submit name=save value=\"Connect to Server\">";
+                   echo "</td></tr>";
+                   echo "</table>";
+                   echo "</form>";
+                   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 "<form method=\"post\" action=\"import.php\">";
+                   echo "<table cellpadding=5>";
+        echo "<tr><td align=right>";
+                   echo "Choose a category of questions to import:</td><td>";
+                   asort($question_categories);
+                   choose_from_menu($question_categories, "question_category","All Categories","All Categories", "", "allcategories");
+                   echo " <input name=\"filename\" type=\"hidden\" value=\"".$filename."\">";
+                   echo " <input name=\"category\" type=\"hidden\" value=\"".$category->id."\">";
+                   echo " <input name=\"format\" type=\"hidden\" value=\"".$form->format."\">";
+                   if (PHP_OS == "Linux") {
+                           echo " <input name=\"hostname\" type=\"hidden\" value=\"".stripslashes(trim($hostname))."\">";
+                           echo " <input name=\"mdbpath\" type=\"hidden\" value=\"".stripslashes($mdbpath)."\">";
+                           echo " <input name=\"mdapath\" type=\"hidden\" value=\"".stripslashes($mdapath)."\">";
+                   }
+                   echo "</td><td>&nbsp;</td>";
+                   echo "</tr><tr><td>&nbsp;</td><td>";
+                   echo " <input type=submit name=save value=\"Import Questions\">";
+                   echo "</td></tr>";
+                   echo "</table>";
+                   echo "</form>";
+                   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 "<pre>";echo htmlspecialchars($qrec[2]); echo "</pre>";
+                               $question->questiontext = addslashes(trim($qrec[2]));
+               //                      echo "<pre>";echo $question->questiontext; echo "</pre>";
+                               $question->name = preg_replace("/<br>/", "", $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("/<br>/", "", $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 "<pre>";echo $question->questiontext; echo "</pre>";
+                               $question->name = preg_replace("/<br>/", "", $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;$i<count($answers);$i++) {
+                                                               $question->answer[$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 "<hr><p><b>$count</b>. ".stripslashes($question->questiontext)."</p>";
+          $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 "<BR>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;$i<count($result);$i++) {
+                                               foreach (array_keys($result[$i]) as $j) {
+                                                       $records[$i][$j-1] = $result[$i][$j];
+                                               }
+                                       }
+                         } else {
+               $this->err("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;$i<count($result);$i++) {
+                                               $categories[$result[$i][0]] = $result[$i][0];
+                                       }
+                       }  // PHP_OS TEST
+                       return $categories;
+    }
+               
+               function query_linux($sql, $mdbpath, $mdapath, $hostname) {
+                               global $result;
+                               include_once("odbcsocketserver.class.php");
+                               // set up socket server object to connect to remote host
+                               $oTest = new ODBCSocketServer;
+                               //Set the Hostname, port, and connection string
+                         $oTest->sHostName = $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 "<br>count of rows is ". count ($result);
+                                       echo "<pre>\n";
+                                       $qResult = HtmlSpecialChars($qResult);
+                                       echo $qResult;
+                                       echo "\n</pre>";
+*/
+                               xml_parser_free($xml_parser);
+//     $sResult = HtmlSpecialChars($qResult);
+       //echo("<pre>");
+//     echo($sResult);
+//     echo("</pre>");
+
+                                       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 "<font color=\"#990000\">";
+             echo "<p>Error: $message</p>";
+             echo "<p>ODBC File DSN: $dsn<br />";
+             echo "</font>";
+    }          
+
+               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 (file)
index 05ed991..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?PHP  // $Id$ 
-
-////////////////////////////////////////////////////////////////////////////
-/// CUSTOM FORMAT
-///
-/// This format provides a starting point for creating your own 
-/// import format.
-/// 
-/// 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.
-///
-////////////////////////////////////////////////////////////////////////////
-
-// Based on default.php, included by ../import.php
-
-class quiz_file_format extends quiz_default_format {
-
-
-
-}
-
-?>
diff --git a/mod/quiz/format/gift/format.php b/mod/quiz/format/gift/format.php
new file mode 100755 (executable)
index 0000000..3a5065f
--- /dev/null
@@ -0,0 +1,274 @@
+<?PHP  // gift2
+// version 1.0 BETA 2
+
+////////////////////////////////////////////////////////////////////////////
+/// GIFT: General Import Format Template
+/// 
+/// The GIFT format is a quick, easy to use method for teachers 
+/// writing questions as a text file. It supports true-false, 
+/// short answer and multiple-choice questions, as well as insertion 
+/// of a blank line for the missing word format. Below are examples 
+/// of the following question types: multiple choice, missing word, 
+/// true-false and short-answer.
+/// 
+///     Who's buried in Grant's tomb?{~Grant ~Jefferson =no one}     
+///     Grant is {~buried =entombed ~living} in Grant's tomb.
+///     Grant is buried in Grant's tomb.{FALSE}
+///     Who's buried in Grant's tomb?{=no one =nobody}
+///
+/// Optional question names are enclosed in double colon(::). 
+/// Answer feedback is indicated with hash mark (#).
+/// Percentage answer weights immediately follow the tilde (for multiple
+/// choice) or equal sign (for short answer), and are enclosed
+/// in percent signs (% %).
+/// 
+///     ::Grant's Tomb::Grant is {
+///     ~buried#No one is buried there.
+///     =entombed#Right answer!
+///     ~living#We hope not!
+///     } in Grant's tomb.
+/// 
+///     Difficult question.{~wrong answer#comment on wrong answer 
+///     ~%50%half credit answer =full credit answer#well done!}
+///     
+///     ::Jesus' hometown:: Jesus Christ was from {
+///     =Nazareth#Yes! That's right!
+///     =%75%Nazereth#Right, but misspelled.
+///     =%25%Bethlehem#He was born here, but not raised here.}
+/// 
+/// This filter was written through the collaboration of numerous 
+/// members of the Moodle community. It was originally based on 
+/// the missingword format. In July 2003, Thomas Robb wrote the 
+/// original code for the percentage answer weight parser and comment 
+/// insertion. Paul Tsuchido Shew rewrote the filter in December 2003 
+/// incorporating community suggestions for a more robust question format, 
+/// and adding the question name parser, additional question types
+/// and other features.
+//////////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+class quiz_file_format extends quiz_default_format {
+
+       function answerweightparser(&$answer) {
+               $answer = substr($answer, 1);                                                   // removes initial %
+               $end_position  = strpos($answer, "%");
+               $answer_weight = substr($answer, 0, $end_position);             // gets weight as integer
+               $answer_weight = $answer_weight/100;                                    // converts to percent
+               $answer = substr($answer, $end_position+1);                             // cleans up answer
+               ///     To enable multiple answers (if fractional answer weights are assigned) 
+               ///     uncomment the following three lines.
+               /// if ($answer_weight > 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 "<P>$text<P>Could not find a {";
+            }
+            return false;
+        }
+
+        $answerfinish = strpos($text, "}");
+        if ($answerfinish === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>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 "<P>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 "<P>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 (file)
index 0000000..7014b72
--- /dev/null
@@ -0,0 +1,156 @@
+<?PHP  // $Id$ 
+/// Modified by Tom Robb 12 June 2003 to include percentage and comment insertion
+/// facility.
+
+////////////////////////////////////////////////////////////////////////////
+/// MISSING WORD FORMAT
+///
+/// This Moodle class provides all functions necessary to import and export 
+/// one-correct-answer multiple choice questions in this format:
+///
+///    As soon as we begin to explore our body parts as infants
+///    we become students of {=anatomy and physiology ~reflexology 
+///    ~science ~experiment}, and in a sense we remain students for life.
+/// 
+/// Each answer is separated with a tilde ~, and the correct answer is 
+/// prefixed with an equals sign =
+///
+/// Percentage weights can be included by following the tilde with the
+/// desired percent.  Comments can be included for each choice by following
+/// the comment with a hash mark ("#") and the comment.  Example:
+///
+///    This is {=the best answer#comment on the best answer ~75%a good
+///    answer#comment on the good answer ~a wrong one#comment on the bad answer}
+///
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+class quiz_file_format extends quiz_default_format {
+
+    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 added by T Robb
+        $comment = NULL;
+        $text = implode(" ", $lines);
+
+        /// Find answer section
+
+        $answerstart = strpos($text, "{");
+        if ($answerstart === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>Could not find a {";
+            }
+            return false;
+        }
+
+        $answerfinish = strpos($text, "}");
+        if ($answerfinish === false) {
+            if ($this->displayerrors) {
+                echo "<P>$text<P>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 "<P>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 (file)
index 0000000..a7fd72e
--- /dev/null
@@ -0,0 +1,162 @@
+<?PHP  // $Id$ 
+
+////////////////////////////////////////////////////////////////////////////
+/// MULTIANSWER FORMAT
+///
+/// Created by Henrik Kaipe
+///
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+    // REGULAR EXPRESSION CONSTANTS
+    // I do not know any way to make this easier
+    // Regexes are always awkard when defined but more comprehensible
+    // when used as constants in the executive code
+
+// ANSWER_ALTERNATIVE regexes
+
+define("ANSWER_ALTERNATIVE_FRACTION_REGEX",
+       '=|%(-?[0-9]+)%');
+define("ANSWER_ALTERNATIVE_ANSWER_REGEX",
+        '[^~#}]+');
+define("ANSWER_ALTERNATIVE_FEEDBACK_REGEX",
+        '[^~}]*');
+define("ANSWER_ALTERNATIVE_REGEX",
+       '(' . ANSWER_ALTERNATIVE_FRACTION_REGEX .')?'
+       . '(' . ANSWER_ALTERNATIVE_ANSWER_REGEX . ')'
+       . '(#(' . ANSWER_ALTERNATIVE_FEEDBACK_REGEX .'))?');
+
+// Parenthesis positions for ANSWER_ALTERNATIVE_REGEX
+define("ANSWER_ALTERNATIVE_REGEX_PERCENTILE_FRACTION", 2);
+define("ANSWER_ALTERNATIVE_REGEX_FRACTION", 1);
+define("ANSWER_ALTERNATIVE_REGEX_ANSWER", 3);
+define("ANSWER_ALTERNATIVE_REGEX_FEEDBACK", 5);
+
+// NUMBER_FORMATED_ALTERNATIVE_ANSWER_REGEX is used
+// for identifying numerical answers in ANSWER_ALTERNATIVE_REGEX_ANSWER
+define("NUMBER_REGEX",
+        '-?(([0-9]+[.,]?[0-9]*|[.,][0-9]+)([eE][-+]?[0-9]+)?)');
+define("NUMERICAL_ALTERNATIVE_REGEX",
+        '^(' . NUMBER_REGEX . ')(:' . NUMBER_REGEX . ')?$');
+
+// Parenthesis positions for NUMERICAL_FORMATED_ALTERNATIVE_ANSWER_REGEX
+define("NUMERICAL_CORRECT_ANSWER", 1);
+define("NUMERICAL_ABS_ERROR_MARGIN", 6);
+
+// Remaining ANSWER regexes
+define("ANSWER_TYPE_DEF_REGEX",
+       '(NUMERICAL|NM)|(MULTICHOICE|MC)|(SHORTANSWER|SA|MW)');
+define("ANSWER_START_REGEX",
+       '\{([0-9]*):(' . ANSWER_TYPE_DEF_REGEX . '):');
+
+define("ANSWER_REGEX",
+        ANSWER_START_REGEX
+        . '(' . ANSWER_ALTERNATIVE_REGEX
+        . '(~'
+        . ANSWER_ALTERNATIVE_REGEX
+        . ')*)}' );
+
+// Parenthesis positions for singulars in ANSWER_REGEX
+define("ANSWER_REGEX_NORM", 1);
+define("ANSWER_REGEX_ANSWER_TYPE_NUMERICAL", 3);
+define("ANSWER_REGEX_ANSWER_TYPE_MULTICHOICE", 4);
+define("ANSWER_REGEX_ANSWER_TYPE_SHORTANSWER", 5);
+define("ANSWER_REGEX_ALTERNATIVES", 6);
+
+
+function extractMultiAnswerQuestion($text) {
+    $question = NULL;
+    $question->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 (file)
index 0000000..9c0bab3
--- /dev/null
@@ -0,0 +1,19 @@
+<?PHP  // $Id$ 
+
+////////////////////////////////////////////////////////////////////////////
+/// IMS QTI FORMAT
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+class quiz_file_format extends quiz_default_format {
+
+    function importpreprocess($category) {
+        global $CFG;
+
+        error("Sorry, this format is not yet implemented!", "$CFG->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 (file)
index 0000000..1b8ded9
--- /dev/null
@@ -0,0 +1,19 @@
+<?PHP  // $Id$ 
+
+////////////////////////////////////////////////////////////////////////////
+/// WEBCT FORMAT
+////////////////////////////////////////////////////////////////////////////
+
+// Based on format.php, included by ../../import.php
+
+class quiz_file_format extends quiz_default_format {
+
+    function importpreprocess($category) {
+        global $CFG;
+
+        error("Sorry, this format is not yet implemented!", "$CFG->wwwroot/mod/quiz/import.php?category=$category->id");
+    }
+
+}
+
+?>
index 8259a9c64498940d310392d6d41586aec4ea3cad..9219963cd8da45714867b8cc2165ab743f98d22a 100644 (file)
 
         } 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();
 
         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");
     echo "<tr><td align=right>";
     print_string("category", "quiz");
     echo ":</td><td>";
-    asort($QUIZ_FILE_FORMAT);
     choose_from_menu($categories, "category", "$category->id", "");
     echo "</tr>";
 
     echo "<tr><td align=right>";
     print_string("fileformat", "quiz");
     echo ":</td><td>";
-    choose_from_menu($QUIZ_FILE_FORMAT, "format", "custom", "");
+    choose_from_menu($fileformatnames, "format", "gift", "");
     helpbutton("import", $strimportquestions, "quiz");
     echo "</tr>";
 
index dc45f5462c53a641e8c4bc036e5b2b4d072cf7ec..3238ac65324330ffe4be83850b8532d96673e00e 100644 (file)
@@ -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