]> git.mjollnir.org Git - moodle.git/commitdiff
New question type - multianswer
authorkaipe <kaipe>
Fri, 1 Aug 2003 08:37:11 +0000 (08:37 +0000)
committerkaipe <kaipe>
Fri, 1 Aug 2003 08:37:11 +0000 (08:37 +0000)
mod/quiz/attempt.php
mod/quiz/db/mysql.php
mod/quiz/db/mysql.sql
mod/quiz/editmultianswer.php [new file with mode: 0644]
mod/quiz/format/multianswer.php [new file with mode: 0644]
mod/quiz/lib.php
mod/quiz/question.php
mod/quiz/version.php

index c16c8818ef96fcbe4fa70174ed5095f62a78248b..326cb5ee75f2dd6ed7ec232ff951ed5fb3fb8c82 100644 (file)
             } else if (ereg('^q([0-9]+)r([0-9]+)$', $key, $keyregs)) { // Random-style answers
                 $questions[$keyregs[1]]->answer[] = "$keyregs[2]-$value";
         
+            } else if (ereg('^q([0-9]+)ma([0-9]+)$', $key, $keyregs)) { // Multi-answer questions
+                $questions[$keyregs[1]]->answer[] = "$keyregs[2]-$value";
+
             } else if ('shuffleorder' == $key) {
                 $shuffleorder = explode(",", $value);   // Actual order questions were given in
             
index f75ca85afec91c859762378818ad870229216697..1b90cf4de68ff1cdd9e43049309b391aaa84b5c1 100644 (file)
@@ -92,7 +92,7 @@ function quiz_upgrade($oldversion) {
         table_column("quiz", "", "shuffleanswers", "INTEGER", "4", "UNSIGNED", "0", "NOT NULL", "shufflequestions");
     }
 
-       if ($oldversion < 2003071001) {
+    if ($oldversion < 2003071001) {
 
         modify_database ("", " CREATE TABLE `prefix_quiz_numerical` (
                                `id` int(10) unsigned NOT NULL auto_increment,
@@ -105,10 +105,23 @@ function quiz_upgrade($oldversion) {
                              ) TYPE=MyISAM COMMENT='Options for numerical questions'; ");
     }
 
-       if ($oldversion < 2003072400) {
+    if ($oldversion < 2003072400) {
         execute_sql(" INSERT INTO {$CFG->prefix}log_display VALUES ('quiz', 'review', 'quiz', 'name') ");
     }
 
+    if ($oldversion < 2003072901) {
+        modify_database ("", " CREATE TABLE `prefix_quiz_multianswers` (
+                               `id` int(10) unsigned NOT NULL auto_increment,
+                                `question` int(10) unsigned NOT NULL default '0',
+                                `answers` varchar(255) NOT NULL default '',
+                                `positionkey` varchar(255) NOT NULL default '',
+                                `answertype` smallint(6) NOT NULL default '0',
+                                `norm` int(10) unsigned NOT NULL default '1',
+                                PRIMARY KEY  (`id`),
+                                KEY `question` (`question`)
+                              ) TYPE=MyISAM COMMENT='Options for multianswer questions'; ");
+    }
+
     return true;
 }
 
index c35d63fcfb6c795ea37f9818123d5b736dd0b52b..4ebbb36e56104f98c2d6fd572f1cb3d0b726b4fa 100644 (file)
@@ -243,7 +243,23 @@ CREATE TABLE `prefix_quiz_truefalse` (
   PRIMARY KEY  (`id`),
   KEY `question` (`question`)
 ) TYPE=MyISAM COMMENT='Options for True-False questions';
+# --------------------------------------------------------
 
+#
+# Table structure for table `quiz_multianswers`
+#
+
+CREATE TABLE `prefix_quiz_multianswers` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `question` int(10) unsigned NOT NULL default '0',
+  `answers` varchar(255) NOT NULL default '',
+  `positionkey` varchar(255) NOT NULL default '',
+  `answertype` smallint(6) NOT NULL default '0',
+  `norm` int(10) unsigned NOT NULL default '1',
+  PRIMARY KEY  (`id`),
+  KEY `question` (`question`)
+) TYPE=MyISAM COMMENT='Options for multianswer questions'
+# --------------------------------------------------------
 
 INSERT INTO prefix_log_display VALUES ('quiz', 'view', 'quiz', 'name');
 INSERT INTO prefix_log_display VALUES ('quiz', 'report', 'quiz', 'name');
diff --git a/mod/quiz/editmultianswer.php b/mod/quiz/editmultianswer.php
new file mode 100644 (file)
index 0000000..da16220
--- /dev/null
@@ -0,0 +1 @@
+<?PHP // $Id$\r\r    require_once("../../config.php");\r    require_once("lib.php");\r    require_once("format/default.php");\r    require_once("format/multianswer.php");\r    require_once("../../files/mimetypes.php");\r\r    if ($form = data_submitted("nomatch")) {\r\r        // Standard checks\r        if (! $category = get_record("quiz_categories", "id", $form->category)) {\r            error("This question doesn't belong to a valid category!");\r        }\r        if (! $course = get_record("course", "id", $category->course)) {\r            error("This question category doesn't belong to a valid course!");\r        }\r        require_login($course->id);\r        if (!isteacher($course->id)) {\r            error("You can't modify these questions!");\r        }\r\r        $question = extractMultiAnswerQuestion($form->questiontext);\r        $question->id = $form->id;\r        $question->qtype = $form->qtype;\r        $question->name = $form->name;\r        $question->category = $form->category;\r\r        if (empty($form->image)) {\r            $question->image = "";\r        } else {\r            $question->image = $form->image;\r        }\r\r        // Formcheck\r        $err = array();\r        if (empty($question->name)) {\r            $err["name"] = get_string("missingname", "quiz");\r        }\r        if (empty($question->questiontext)) {\r            $err["questiontext"] = get_string("missingquestiontext", "quiz");\r        }\r        if ($err) { // Formcheck failed\r            $category = $form->category;\r            notify(get_string("someerrorswerefound"));\r            unset($_POST);\r            require('question.php');\r            exit;\r\r        } else {\r\r            if (!empty($question->id)) { // Question already exists\r                if (!update_record("quiz_questions", $question)) {\r                    error("Could not update question!");\r                }\r            } else {         // Question is a new one\r                if (!$question->id = insert_record("quiz_questions", $question)) {\r                    error("Could not insert new question!");\r                }\r            }\r    \r            // Now to save all the answers and type-specific options\r            $result = quiz_save_question_options($question);\r\r            if (!empty($result->error)) {\r                error($result->error);\r            }\r\r            if (!empty($result->notice)) {\r                notice_yesno($result->notice, "question.php?id=$question->id", "edit.php");\r                print_footer($course);\r                exit;\r            }\r    \r            redirect("edit.php");\r        }\r\r    } else if ($question->questiontext and $question->id) {\r        $answers = quiz_get_answers($question);\r\r        foreach ($answers as $multianswer) {\r            $parsableanswerdef = '{' . $multianswer->norm . ':';\r            switch ($multianswer->answertype) {\r                case MULTICHOICE:\r                    $parsableanswerdef .= 'MULTICHOICE:';\r                    break;\r                case SHORTANSWER:\r                    $parsableanswerdef .= 'SHORTANSWER:';\r                    break;\r                case NUMERICAL:\r                    $parsableanswerdef .= 'NUMERICAL:';\r                    break;\r                default:\r                    error("answertype $multianswer->answertype not recognized");\r            }\r            $separator= '';\r            foreach ($multianswer->subanswers as $subanswer) {\r                $parsableanswerdef .= $separator\r                        . '%' . round(100*$subanswer->fraction) . '%';\r                $parsableanswerdef .= $subanswer->answer;\r                if ($subanswer->min || $subanswer->max) {\r                    // Special for numerical answers:\r                    $errormargin = $subanswer->answer - $subanswer->min;\r                    $parsableanswerdef .= ":$errormargin";\r                }\r                if ($subanswer->feedback) {\r                    $parsableanswerdef .= "#$subanswer->feedback";\r                }\r                $separator = '~';\r            }\r            $parsableanswerdef .= '}';\r            $question->questiontext = str_replace\r                    ("{#$multianswer->positionkey}", $parsableanswerdef,\r                     $question->questiontext);\r        }\r    }\r\r\r?>\r\r<FORM name="theform" method="post" <?=$onsubmit ?> action="editmultianswer.php">\r\r<CENTER>\r\r<TABLE cellpadding=5>\r\r<TR valign=top>\r\r    <TD align=right><P><B><? print_string("category", "quiz") ?>:</B></P></TD>\r\r    <TD>\r\r    <?  choose_from_menu($categories, "category", "$question->category", ""); ?>\r\r    </TD>\r\r</TR>\r\r<TR valign=top>\r\r    <TD align=right><P><B><? print_string("questionname", "quiz") ?>:</B></P></TD>\r\r    <TD>\r\r        <INPUT type="text" name="name" size=40 value="<? p($question->name) ?>">\r\r        <? if (isset($err["name"])) formerr($err["name"]); ?>\r\r    </TD>\r\r</TR>\r\r<TR valign=top>\r\r    <TD align=right><P><B><? print_string("question", "quiz") ?>:</B></P></TD>\r\r    <TD>\r\r        <? if (isset($err["questiontext"])) {\r\r               formerr($err["questiontext"]); \r\r               echo "<BR \>";\r\r           }\r\r           print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);\r\r           if ($usehtmleditor) {\r\r               helpbutton("richtext", get_string("helprichtext"), "moodle");\r\r           } else {\r\r               helpbutton("text", get_string("helptext"), "moodle");\r\r           }\r\r        ?>\r\r    </TD>\r\r</TR>\r\r<TR valign=top>\r\r    <TD align=right><P><B><? print_string("imagedisplay", "quiz") ?>:</B></P></TD>\r\r    <TD>\r\r    <?  if (empty($images)) {\r\r            print_string("noimagesyet");\r\r        } else {\r\r            choose_from_menu($images, "image", "$question->image", get_string("none"),"","");\r\r        }\r\r    ?>\r\r    </TD>\r\r</TR>\r\r</TABLE>\r\r\r\r<INPUT type="hidden" name=id value="<? p($question->id) ?>">\r\r<INPUT type="hidden" name=qtype value="<? p($question->qtype) ?>">\r\r<INPUT type="hidden" name=defaultgrade value="<? p($question->defaultgrade) ?>">\r\r<INPUT type="submit" value="<? print_string("savechanges") ?>">\r\r\r\r</CENTER>\r\r</FORM>\r\r<? \r\r   if ($usehtmleditor) { \r\r       print_richedit_javascript("theform", "questiontext", "no");\r\r   }\r\r?>\r
\ No newline at end of file
diff --git a/mod/quiz/format/multianswer.php b/mod/quiz/format/multianswer.php
new file mode 100644 (file)
index 0000000..de161ea
--- /dev/null
@@ -0,0 +1,162 @@
+<?PHP  // $Id$ 
+
+////////////////////////////////////////////////////////////////////////////
+/// MULTIANSWER FORMAT
+///
+/// Created by Henrik Kaipe
+///
+////////////////////////////////////////////////////////////////////////////
+
+// Based on default.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)|(MULTICHOICE)|(SHORTANSWER)');
+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;
+    }
+}
+
+?>
index fc6a41b6811782b99e8274b3421996e5e354c7b6..5064001b031b4df0830d35a1ad7229447a79e07d 100644 (file)
@@ -21,6 +21,7 @@ define("MATCH",         "5");
 define("RANDOMSAMATCH", "6");
 define("DESCRIPTION",   "7");
 define("NUMERICAL",     "8");
+define("MULTIANSWER",   "9");
 
 $QUIZ_QUESTION_TYPE = array ( MULTICHOICE   => get_string("multichoice", "quiz"),
                               TRUEFALSE     => get_string("truefalse", "quiz"),
@@ -29,13 +30,15 @@ $QUIZ_QUESTION_TYPE = array ( MULTICHOICE   => get_string("multichoice", "quiz")
                               MATCH         => get_string("match", "quiz"),
                               DESCRIPTION   => get_string("description", "quiz"),
                               RANDOM        => get_string("random", "quiz"),
-                              RANDOMSAMATCH => get_string("randomsamatch", "quiz")
+                              RANDOMSAMATCH => get_string("randomsamatch", "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"
+                            "aon" => "AON",
+                            "multianswer" => get_string("multianswer", "quiz")
                             );
 
 define("QUIZ_PICTURE_MAX_HEIGHT", "600");   // Not currently implemented
@@ -258,17 +261,24 @@ function quiz_get_grade_records($quiz) {
                               AND qg.userid = u.id");
 }
 
-function quiz_get_answers($question) {
+function quiz_get_answers($question, $answerids=NULL) {
 // Given a question, returns the correct answers for a given question
     global $CFG;
 
+    if (empty($answerids)) {
+        $answeridconstraint = '';
+    } else {
+        $answeridconstraint = " AND a.id IN ($answerids) ";
+    }
+
     switch ($question->qtype) {
         case SHORTANSWER:       // Could be multiple answers
             return get_records_sql("SELECT a.*, sa.usecase
                                       FROM {$CFG->prefix}quiz_shortanswer sa,  
                                            {$CFG->prefix}quiz_answers a
                                      WHERE sa.question = '$question->id' 
-                                       AND sa.question = a.question ");
+                                       AND sa.question = a.question "
+                                  . $answeridconstraint);
 
         case TRUEFALSE:         // Should be always two answers
             return get_records("quiz_answers", "question", $question->id);
@@ -278,7 +288,8 @@ function quiz_get_answers($question) {
                                       FROM {$CFG->prefix}quiz_multichoice mc, 
                                            {$CFG->prefix}quiz_answers a
                                      WHERE mc.question = '$question->id' 
-                                       AND mc.question = a.question ");
+                                       AND mc.question = a.question "
+                                  . $answeridconstraint);
 
         case MATCH:
             return get_records("quiz_match_sub", "question", $question->id);
@@ -296,7 +307,8 @@ function quiz_get_answers($question) {
                                       FROM {$CFG->prefix}quiz_numerical n,
                                            {$CFG->prefix}quiz_answers a
                                      WHERE a.question = '$question->id'
-                                       AND n.answer = a.id ");
+                                       AND n.answer = a.id "
+                                  . $answeridconstraint);
 
         case DESCRIPTION:
             return true; // there are no answers for description
@@ -305,6 +317,21 @@ function quiz_get_answers($question) {
             return quiz_get_answers
                     (get_record('quiz_questions', 'id', $question->random));
 
+        case MULTIANSWER:       // Includes subanswers
+            $multianswers = get_records('quiz_multianswers',
+                                   'question', $question->id);
+            $virtualquestion->id = $question->id;
+
+            $answers = array();
+            foreach ($multianswers as $multianswer) {
+                $virtualquestion->qtype = $multianswer->answertype;
+                // Recursive call for subanswers
+                $multianswer->subanswers = quiz_get_answers
+                        ($virtualquestion, $multianswer->answers);
+                $answers[] = $multianswer;
+            }
+            return $answers;
+
         default:
             return false;
     }
@@ -317,7 +344,7 @@ function quiz_get_attempt_responses($attempt, $quiz) {
 // for regrading using quiz_grade_attempt_results()
     global $CFG;
    
-    if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext, r.answer 
+    if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext, q.defaultgrade, r.answer 
                                         FROM {$CFG->prefix}quiz_responses r, 
                                              {$CFG->prefix}quiz_questions q
                                        WHERE r.attempt = '$attempt->id' 
@@ -398,6 +425,9 @@ function quiz_print_question_icon($question, $editlink=true) {
         case NUMERICAL:
             echo '<img border=0 height=16 width=16 src="pix/nu.gif">';
             break;
+        case MULTIANSWER:
+            echo '<img border=0 height=16 width=16 src="pix/mu.gif">';
+            break;
     }
     if ($editlink) {
         echo "</a>\n";
@@ -727,6 +757,73 @@ function quiz_print_question($number, $question, $grade, $courseid,
            echo "</table>";
            break;
 
+       case MULTIANSWER:
+           // For this question type, we better print the image on top:
+           if ($question->image) {
+               print_file_picture($question->image, $question->course);
+           }
+
+            $qtextremaining = text_to_html($question->questiontext);
+            // The regex will recognize text snippets of type {#X} where the X can be any text not containg } or white-space characters.
+            while (ereg('\{#([^[:space:]}]*)}', $qtextremaining, $regs)) {
+
+                $qtextsplits = explode($regs[0], $qtextremaining, 2);
+                echo $qtextsplits[0];
+                $qtextremaining = $qtextsplits[1];
+
+                $multianswer = get_record('quiz_multianswers',
+                                          'question', $question->id,
+                                          'positionkey', $regs[1]);
+                
+                $inputname= " name=\"q{$realquestion->id}ma$multianswer->id\" ";
+                
+                if (!empty($response)
+                    && $responseitems = explode('-', array_shift($response), 2))
+                {
+                    $responsefractiongrade = (float)$responseitems[0];
+                    $actualresponse = $responseitems[1];
+
+                    if (1.0 == $responsefractiongrade) {
+                        $style = '"background-color:lime"';
+                    } else if (0.0 < $responsefractiongrade) {
+                        $style = '"background-color:yellow"';
+                    } else { // The response must have been totally wrong:
+                        $style = '"background-color:red"';
+                    }
+                } else {
+                    $responsefractiongrade = 0.0;
+                    $actualresponse = '';
+                    $style = '"background-color:white"';
+                }
+
+                switch ($multianswer->answertype) {
+                    case SHORTANSWER:
+                    case NUMERICAL:
+                        echo " <input style=$style $inputname value=\"$actualresponse\" type=\"TEXT\" size=\"8\"/> ";
+                    break;
+                    case MULTICHOICE:
+                        echo (" <select style=$style $inputname>");
+                        $answers = get_records_list("quiz_answers", "id", $multianswer->answers);
+                        echo ('<option></option>'); // Default empty option 
+                        foreach ($answers as $answer) {
+                            if ($answer->id == $actualresponse) {
+                                $selected = 'selected';
+                            } else {
+                                $selected = '';
+                            }
+                            echo "<option value=\"$answer->id\" $selected>$answer->answer</option>";
+                        }
+                        echo ("</select> ");
+                    break;
+                    default:
+                        error("Unable to recognized answertype $answer->answertype");
+                    break;
+                }
+            }
+            // Print the final piece of question text:
+            echo $qtextremaining;
+            break;
+
        case RANDOM:
            echo "<P>Random questions should not be printed this way!</P>";
            break;
@@ -1561,6 +1658,46 @@ function quiz_grade_attempt_question_result($question, $answers) {
             }
             break;
 
+        case MULTIANSWER:
+            // Default setting that avoids a possible divide by zero:
+            $subquestion->grade = 1.0;
+
+            foreach ($question->answer as $questionanswer) {
+                
+                // Resetting default values for subresult:
+                $subresult->grade = 0.0;
+                $subresult->correct = array();
+                $subresult->feedback = array();
+
+                // Resetting subquestion responses:
+                $subquestion->answer = array();
+
+                $qarr = explode('-', $questionanswer, 2);
+                $subquestion->answer[] = $qarr[1];  // Always single answer for subquestions
+                foreach ($answers as $multianswer) {
+                    if ($multianswer->id == $qarr[0]) {
+                        $subquestion->qtype = $multianswer->answertype;
+                        $subquestion->grade = $multianswer->norm;
+                        $subresult = quiz_grade_attempt_question_result
+                                ($subquestion, $multianswer->subanswers);
+                        break;
+                    }
+                }
+
+                // Summarize subquestion results:
+                $grade += $subresult->grade;
+                $feedback[] = $subresult->feedback[0];
+                $correct[]  = $subresult->correct[0];
+
+                // Each response instance also contains the partial
+                // fraction grade for the response:
+                $response[] = $subresult->grade/$subquestion->grade
+                              . '-' . $subquestion->answer[0];
+            }
+            // Normalize grade:
+            $grade *= $question->grade/($question->defaultgrade);
+            break;
+
         case DESCRIPTION:  // Descriptions are not graded.
             break;
 
@@ -1999,6 +2136,52 @@ function quiz_save_question_options($question) {
             }
         break;
 
+        case MULTIANSWER:
+            if (!$oldmultianswers = get_records("quiz_multianswers", "question", $question->id, "id ASC")) {
+                $oldmultianswers = array();
+            }
+
+            // Insert all the new multi answers
+            foreach ($question->answers as $dataanswer) {
+                if ($oldmultianswer = array_shift($oldmultianswers)) {  // Existing answer, so reuse it
+                    $multianswer = $oldmultianswer;
+                    $multianswer->positionkey = $dataanswer->positionkey;
+                    $multianswer->norm = $dataanswer->norm;
+                    $multianswer->answertype = $dataanswer->answertype;
+
+                    if (! $multianswer->answers = quiz_save_multianswer_alternatives
+                            ($question->id, $dataanswer->answertype,
+                             $dataanswer->alternatives, $oldmultianswer->answers))
+                    {
+                        $result->error = "Could not update multianswer alternatives! (id=$multianswer->id)";
+                        return $result;
+                    }
+                    if (!update_record("quiz_multianswers", $multianswer)) {
+                        $result->error = "Could not update quiz multianswer! (id=$multianswer->id)";
+                        return $result;
+                    }
+                } else {    // This is a completely new answer
+                    unset($multianswer);
+                    $multianswer->question = $question->id;
+                    $multianswer->positionkey = $dataanswer->positionkey;
+                    $multianswer->norm = $dataanswer->norm;
+                    $multianswer->answertype = $dataanswer->answertype;
+
+                    if (! $multianswer->answers = quiz_save_multianswer_alternatives
+                            ($question->id, $dataanswer->answertype,
+                             $dataanswer->alternatives))
+                    {
+                        $result->error = "Could not insert multianswer alternatives! (questionid=$question->id)";
+                        return $result;
+                    }
+                    if (!insert_record("quiz_multianswers", $multianswer)) {
+                        $result->error = "Could not insert quiz multianswer!";
+                        return $result;
+                    }
+                }
+            }
+        break;
+
         case RANDOM:
         break;
 
@@ -2031,6 +2214,145 @@ function quiz_remove_unwanted_questions(&$questions, $quiz) {
     }
 }
 
+function quiz_save_multianswer_alternatives
+        ($questionid, $answertype, $alternatives, $oldalternativeids= NULL)
+{
+// Returns false if something goes wrong,
+// otherwise the ids of the answers.
 
+    if (empty($oldalternativeids)
+        or $oldalternatives =
+            get_records_list('quiz_answers', 'id', $oldalternativeids))
+    {
+        $oldalternatives = array();
+    }
+
+    $alternativeids = array();
+
+    foreach ($alternatives as $altdata) {
+
+        if ($altold = array_shift($oldalternatives)) { // Use existing one...
+            $alt = $altold;
+            $alt->answer = $altdata->answer;
+            $alt->fraction = $altdata->fraction;
+            $alt->feedback = $altdata->feedback;
+            if (!update_record("quiz_answers", $alt)) {
+                return false;
+            }
+
+        } else { // Completely new one
+            unset($alt);
+            $alt->question= $questionid;
+            $alt->answer = $altdata->answer;
+            $alt->fraction = $altdata->fraction;
+            $alt->feedback = $altdata->feedback;
+            if (! $alt->id = insert_record("quiz_answers", $alt)) {
+                return false;
+            }
+        }
+
+        // For the answer type numerical, each alternative has individual options:
+        if ($answertype == NUMERICAL) {
+            if ($numericaloptions =
+                    get_record('quiz_numerical', 'answer', $alt->id))
+            {
+                // Reuse existing numerical options
+                $numericaloptions->min = $altdata->min;
+                $numericaloptions->max = $altdata->max;
+                if (!update_record('quiz_numerical', $numericaloptions)) {
+                    return false;
+                }
+            } else {
+                // New numerical options
+                $numericaloptions->answer = $alt->id;
+                $numericaloptions->question = $questionid;
+                $numericaloptions->min = $altdata->min;
+                $numericaloptions->max = $altdata->max;
+                if (!insert_record("quiz_numerical", $numericaloptions)) {
+                    return false;
+                }
+            }
+        } else { // Delete obsolete numerical options
+            delete_records('quiz_numerical', 'answer', $alt->id);
+        } // end if NUMERICAL
+
+        $alternativeids[] = $alt->id;
+    } // end foreach $alternatives
+    $answers = implode(',', $alternativeids);
+
+    // Removal of obsolete alternatives from answers and quiz_numerical:
+    while ($altobsolete = array_shift($oldalternatives)) {
+        delete_records("quiz_answers", "id", $altobsolete->id);
+        
+        // Possibly obsolute numerical options are also to be deleted:
+        delete_records("quiz_numerical", 'answer', $alt->id);
+    }
+
+    // Common alternative options and removal of obsolete options
+    switch ($answertype) {
+        case NUMERICAL:
+            if (!empty($oldalternativeids)) {
+                delete_records('quiz_shortanswer', 'answers', 
+$oldalternativeids);
+                delete_records('quiz_multichoice', 'answers', 
+$oldalternativeids);
+            }
+            break;
+        case SHORTANSWER:
+            if (!empty($oldalternativeids)) {
+                delete_records('quiz_multichoice', 'answers', 
+$oldalternativeids);
+                $options = get_record('quiz_shortanswer',
+                                      'answers', $oldalternativeids);
+            } else {
+                unset($options);
+            }
+            if (empty($options)) {
+                // Create new shortanswer options
+                $options->question = $questionid;
+                $options->usecase = 0;
+                $options->answers = $answers;
+                if (!insert_record('quiz_shortanswer', $options)) {
+                    return false;
+                }
+            } else if ($answers != $oldalternativeids) {
+                // Shortanswer options needs update:
+                $options->answers = $answers;
+                if (!update_record('quiz_shortanswer', $options)) {
+                    return false;
+                }
+            }
+            break;
+        case MULTICHOICE:
+            if (!empty($oldalternativeids)) {
+                delete_records('quiz_shortanswer', 'answers', 
+$oldalternativeids);
+                $options = get_record('quiz_multichoice',
+                                      'answers', $oldalternativeids);
+            } else {
+                unset($options);
+            }
+            if (empty($options)) {
+                // Create new multichoice options
+                $options->question = $questionid;
+                $options->layout = 0;
+                $options->single = 1;
+                $options->answers = $answers;
+                if (!insert_record('quiz_multichoice', $options)) {
+                    return false;
+                }
+            } else if ($answers != $oldalternativeids) {
+                // Multichoice options needs update:
+                $options->answers = $answers;
+                if (!update_record('quiz_multichoice', $options)) {
+                    return false;
+                }
+            }
+            break;
+        default:
+            return false;
+    }
+    return $answers;       
+}
 
 ?>
index dfb40e5d7fb5e561bc2ab64dc5262b4aea82d02a..9aef192cfeac0b172f9761d9e35281bcbb58cded 100644 (file)
             require("description.html");
         break;
 
+        case MULTIANSWER:
+            print_heading_with_help(get_string("editingmultianswer", "quiz"), "multianswer", "quiz");
+            require("editmultianswer.php");
+        break;
+
         case NUMERICAL:
             // This will only support one answer of the type NUMERICAL
             // However, lib.php has support for multiple answers
index 2a26c241c5cea855a1c18ed242fac8e2cc12c2b9..b9e671c07d93f28408573933901beac50a146f08 100644 (file)
@@ -5,7 +5,7 @@
 //  This fragment is called by moodle_needs_upgrading() and /admin/index.php
 ////////////////////////////////////////////////////////////////////////////////
 
-$module->version  = 2003072400;   // The (date) version of this module
+$module->version  = 2003072901;   // The (date) version of this module
 $module->cron     = 0;            // How often should cron check this module (seconds)?
 
 ?>