First version of code to implement new "Random Matching" type

of questions.  These use 2 or more short answer questions at
random to construct a questions where you have to match
answers to questions.  Only lightly tested so far.

Quiz questions can now be edited with Richtext editor.

Sundry little fixes along the way.
This commit is contained in:
moodler
2003-02-24 10:37:56 +00:00
parent 2b051f1c8e
commit 95dbc030a8
16 changed files with 389 additions and 77 deletions
+10
View File
@@ -0,0 +1,10 @@
<p align=center><b>Random Matching questions</b></p>
<p>In response to a question (that may include a image) the respondent
is presented with several questions and several answers. There is one
correct answer for each question.
<p>The respondent selects the answers that match each question.
<p>The questions and answers are randomly drawn from the pool of
"Short Answer" questions in the current category.
+4
View File
@@ -47,6 +47,7 @@ $string['editingquestion'] = "Editing a question";
$string['editingshortanswer'] = "Editing a short answer question";
$string['editingtruefalse'] = "Editing a true/false question";
$string['editingmultichoice'] = "Editing a multiple choice question";
$string['editingrandommatch'] = "Editing a random matching question";
$string['false'] = "False";
$string['feedback'] = "Feedback";
$string['filloutoneanswer'] = "You must fill out at least one possible answer. Answers left blank will not be used.";
@@ -88,6 +89,9 @@ $string['quizclosed'] = "This quiz closed on \$a";
$string['quizopen'] = "Open the quiz";
$string['quiznotavailable'] = "The quiz will not be available until: \$a";
$string['random'] = "Random set";
$string['randommatch'] = "Random Match";
$string['randommatchintro'] = "For each of the following questions, select the matching answer from the menu.";
$string['randommatchnumber'] = "Number of questions to select";
$string['readytosend'] = "You are about to send your whole quiz to be graded. Are you sure you want to continue?";
$string['regrade'] = "Regrade all attempts";
$string['regradecomplete'] = "All attempts have been regraded";
+18 -10
View File
@@ -89,19 +89,27 @@
error("No questions found!");
}
foreach ($rawanswers as $key => $value) { // Parse input for question -> answers
foreach ($rawanswers as $key => $value) { // Parse input for question -> answers
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
if (!isset($questions[$key])) {
if (substr_count($key, "a")) { // checkbox style multiple answers
$check = explode("a", $key);
$key = $check[0];
$value = $check[1];
} else {
error("Answer received for non-existent question ($key)!");
}
if (isset($questions[$key])) { // It's a real question number, not a coded one
$questions[$key]->answer[] = trim($value);
} else if (substr_count($key, "a")) { // Checkbox style multiple answers
$check = explode("a", $key);
$key = $check[0]; // The main question number
$value = $check[1]; // The actual answer
$questions[$key]->answer[] = trim($value);
} else if (substr_count($key, "r")) { // Random-style questions
$check = explode("r", $key);
$key = $check[0]; // The main question
$rand = $check[1]; // The random sub-question
$questions[$key]->answer[] = "$rand-$value";
} else {
error("Answer received for non-existent question ($key)!");
}
$questions[$key]->answer[] = trim($value); // Store answers in array (whitespace trimmed)
}
}
+9
View File
@@ -41,6 +41,15 @@ function quiz_upgrade($oldversion) {
table_column("quiz_questions", "type", "qtype", "INTEGER", "10", "UNSIGNED", "0", "NOT NULL", "");
}
if ($oldversion < 2003022303) {
modify_database ("", "CREATE TABLE `prefix_quiz_randommatch` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`choose` INT UNSIGNED DEFAULT '4' NOT NULL,
PRIMARY KEY ( `id` )
);");
}
return true;
}
+11
View File
@@ -122,6 +122,17 @@ CREATE TABLE `prefix_quiz_question_grades` (
) TYPE=MyISAM COMMENT='The grade for a question in a quiz';
# --------------------------------------------------------
#
# Table structure for table `quiz_randommatch`
#
CREATE TABLE `prefix_quiz_randommatch` (
`id` int(10) unsigned NOT NULL auto_increment,
`question` int(10) unsigned NOT NULL default '0',
`choose` INT UNSIGNED DEFAULT '4' NOT NULL,
PRIMARY KEY ( `id` )
) TYPE=MyISAM COMMENT='Info about a random matching question';
#
# Table structure for table `quiz_questions`
#
+1 -1
View File
@@ -84,7 +84,7 @@
foreach ($rawquestions as $key => $value) { // Parse input for question ids
if (substr($key, 0, 1) == "q") {
$key = substr($key,1);
if ($questions) {
if (!empty($questions)) {
foreach ($questions as $question) {
if ($question == $key) {
continue 2;
-15
View File
@@ -61,21 +61,6 @@ class quiz_default_format {
}
function swapshuffle($array) {
/// Given a simple array, shuffles it up just like shuffle()
/// Unlike PHP's shuffle() ihis function works on any machine.
srand ((double) microtime() * 10000000);
$last = count($array) - 1;
for ($i=0;$i<=$last;$i++) {
$from = rand(0,$last);
$curr = $array[$i];
$array[$i] = $array[$from];
$array[$from] = $curr;
}
return $array;
}
}
?>
+43 -30
View File
@@ -57,38 +57,51 @@ class quiz_file_format extends quiz_default_format {
/// Parse the answers
$answers = explode("~", $answertext);
if (! count($answers)) {
if ($this->displayerrors) {
echo "<P>No answers found in $answertext";
}
return false;
$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->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->single = 1; // Only one answer is allowed
$question->image = ""; // No images with this format
return $question;
}
if (count($answers) == 1) {
return false;
}
$answers = $this->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->qtype = MULTICHOICE;
$question->single = 1; // Only one answer is allowed
$question->image = ""; // No images with this format
return $question;
}
}
?>
+163 -11
View File
@@ -17,9 +17,13 @@ define("SHORTANSWER", "1");
define("TRUEFALSE", "2");
define("MULTICHOICE", "3");
define("RANDOM", "4");
define("MATCH", "5");
define("RANDOMMATCH", "6");
$QUIZ_QUESTION_TYPE = array ( MULTICHOICE => get_string("multichoice", "quiz"),
TRUEFALSE => get_string("truefalse", "quiz"),
SHORTANSWER => get_string("shortanswer", "quiz") );
SHORTANSWER => get_string("shortanswer", "quiz"),
RANDOMMATCH => get_string("randommatch", "quiz") );
$QUIZ_FILE_FORMAT = array ( "custom" => get_string("custom", "quiz"),
"webct" => get_string("webct", "quiz"),
@@ -245,8 +249,9 @@ function quiz_get_grade_records($quiz) {
function quiz_get_answers($question) {
// Given a question, returns the correct answers and grades
global $CFG;
switch ($question->qtype) {
case SHORTANSWER; // Could be multiple answers
case SHORTANSWER: // Could be multiple answers
return get_records_sql("SELECT a.*, sa.usecase, g.grade
FROM {$CFG->prefix}quiz_shortanswer sa,
{$CFG->prefix}quiz_answers a,
@@ -256,7 +261,7 @@ function quiz_get_answers($question) {
AND sa.question = g.question");
break;
case TRUEFALSE; // Should be always two answers
case TRUEFALSE: // Should be always two answers
return get_records_sql("SELECT a.*, g.grade
FROM {$CFG->prefix}quiz_answers a,
{$CFG->prefix}quiz_question_grades g
@@ -264,7 +269,7 @@ function quiz_get_answers($question) {
AND a.question = g.question");
break;
case MULTICHOICE; // Should be multiple answers
case MULTICHOICE: // Should be multiple answers
return get_records_sql("SELECT a.*, mc.single, g.grade
FROM {$CFG->prefix}quiz_multichoice mc,
{$CFG->prefix}quiz_answers a,
@@ -274,10 +279,21 @@ function quiz_get_answers($question) {
AND mc.question = g.question");
break;
case RANDOM:
case RANDOM:
return false; // Not done yet
break;
case RANDOMMATCH: // Could be any of many answers, return them all
return get_records_sql("SELECT a.*, g.grade
FROM {$CFG->prefix}quiz_questions q,
{$CFG->prefix}quiz_answers a,
{$CFG->prefix}quiz_question_grades g
WHERE q.category = '$question->category'
AND q.qtype = ".SHORTANSWER."
AND q.id = a.question
AND g.question = '$question->id'");
break;
default:
return false;
}
@@ -290,7 +306,7 @@ function quiz_get_attempt_responses($attempt) {
// for regrading using quiz_grade_attempt_results()
global $CFG;
if (!$responses = get_records_sql("SELECT q.id, q.qtype, r.answer
if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, r.answer
FROM {$CFG->prefix}quiz_responses r,
{$CFG->prefix}quiz_questions q
WHERE r.attempt = '$attempt->id'
@@ -343,6 +359,9 @@ function quiz_print_question_icon($question) {
case RANDOM:
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rs.gif\">";
break;
case RANDOMMATCH:
echo "<IMG BORDER=0 HEIGHT=16 WIDTH=16 SRC=\"pix/rm.gif\">";
break;
}
echo "</A>\n";
}
@@ -504,6 +523,93 @@ function quiz_print_question($number, $questionid, $grade, $courseid,
echo "<P>Random questions not supported yet</P>";
break;
case RANDOMMATCH:
if (!$options = get_record("quiz_randommatch", "question", $question->id)) {
notify("Error: Missing question options!");
}
echo text_to_html($question->questiontext);
if ($question->image) {
print_file_picture($question->image, $courseid, QUIZ_PICTURE_DEFAULT_HEIGHT);
}
/// First, get all the questions available
$allquestions = get_records_select("quiz_questions",
"category = $question->category AND qtype = ".SHORTANSWER);
if (count($allquestions) < $options->choose) {
notify("Error: could not find enough Short Answer questions in the database!");
notify("Found ".count($allquestions).", need $options->choose.");
break;
}
if (empty($response)) { // Randomly pick the questions
if (!$randomquestions = draw_rand_array($allquestions, $options->choose)) {
notify("Error choosing $options->choose random questions");
break;
}
} else { // Use existing questions
$randomquestions = array();
foreach ($response as $key => $rrr) {
$rrr = explode("-", $rrr);
$randomquestions[$key] = $allquestions[$key];
$responseanswer[$key] = $rrr[1];
}
}
/// For each selected, find the best matching answers
foreach ($randomquestions as $randomquestion) {
$shortanswerquestion = get_record("quiz_shortanswer", "question", $randomquestion->id);
$questionanswers = get_records_list("quiz_answers", "id", $shortanswerquestion->answers);
$bestfraction = 0;
$bestanswer = NULL;
foreach ($questionanswers as $questionanswer) {
if ($questionanswer->fraction > $bestfraction) {
$bestanswer = $questionanswer;
}
}
if (empty($bestanswer)) {
notify("Error: Could not find the best answer for question: ".$randomquestions->name);
break;
}
$randomanswers[$bestanswer->id] = trim($bestanswer->answer);
}
if (!$randomanswers = draw_rand_array($randomanswers, $options->choose)) { // Mix them up
notify("Error randomising answers!");
break;
}
echo "<table border=0 cellpadding=10>";
foreach ($randomquestions as $key => $randomquestion) {
echo "<tr><td align=left valign=top>";
echo $randomquestion->questiontext;
echo "</td>";
echo "<td align=right valign=top>";
if (empty($response)) {
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id");
} else {
if (!empty($correct[$key])) {
if ($randomanswers[$responseanswer[$key]] == $correct[$key]) {
echo "<span=highlight>";
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
echo "</span><br \>";
} else {
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
quiz_print_correctanswer($correct[$key]);
}
} else {
choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]);
}
if (!empty($feedback[$key])) {
quiz_print_comment($feedback[$key]);
}
}
echo "</td></tr>";
}
echo "</table>";
break;
default:
notify("Error: Unknown question type!");
@@ -693,6 +799,9 @@ function quiz_print_question_list($questionlist, $grades) {
echo "<TABLE BORDER=0 CELLPADDING=5 CELLSPACING=2 WIDTH=\"100%\">";
echo "<TR><TH WIDTH=\"*\" COLSPAN=3 NOWRAP>$strorder</TH><TH align=left WIDTH=\"100%\" NOWRAP>$strquestionname</TH><TH width=\"*\" NOWRAP>$strtype</TH><TH WIDTH=\"*\" NOWRAP>$strgrade</TH><TH WIDTH=\"*\" NOWRAP>$stredit</TH></TR>";
foreach ($order as $qnum) {
if (empty($questions[$qnum])) {
continue;
}
$count++;
echo "<TR BGCOLOR=\"$THEME->cellcontent\">";
echo "<TD>$count</TD>";
@@ -1087,24 +1196,47 @@ function quiz_grade_attempt_results($quiz, $questions) {
if (!empty($question->answer)) {
foreach ($question->answer as $questionanswer) {
if ($questionanswer == $answer->id) {
$response[$answer->id] = true;
if ($answer->single) {
$grade = (float)$answer->fraction * $answer->grade;
$response[$answer->id] = true;
continue;
} else {
$grade += (float)$answer->fraction * $answer->grade;
$response[$answer->id] = true;
}
}
}
}
}
break;
case RANDOM:
// Not done yet
case RANDOMMATCH:
$bestanswer = array();
foreach ($answers as $answer) { // Loop through them all looking for correct answers
if (empty($bestanswer[$answer->question])) {
$bestanswer[$answer->question] = 0;
$correct[$answer->question] = "";
}
if ($answer->fraction > $bestanswer[$answer->question]) {
$bestanswer[$answer->question] = $answer->fraction;
$correct[$answer->question] = $answer->answer;
}
}
$answerfraction = 1.0 / (float) count($question->answer);
foreach ($question->answer as $questionanswer) { // For each random answered question
$rqarr = explode('-', $questionanswer); // Extract question/answer.
$rquestion = $rqarr[0];
$ranswer = $rqarr[1];
$response[$rquestion] = $questionanswer;
if (isset($answers[$ranswer])) { // If the answer exists in the list
$answer = $answers[$ranswer];
$feedback[$rquestion] = $answer->feedback;
if ($answer->question == $rquestion) { // Check that this answer matches the question
$grade += (float)$answer->fraction * $answer->grade * $answerfraction;
}
}
}
break;
}
if ($grade < 0.0) { // No negative grades
$grade = 0.0;
@@ -1272,6 +1404,24 @@ function quiz_save_question_options($question) {
}
}
break;
case RANDOMMATCH:
$options->question = $question->id;
$options->choose = $question->choose;
if ($existing = get_record("quiz_randommatch", "question", $options->question)) {
$options->id = $existing->id;
if (!update_record("quiz_randommatch", $options)) {
$result->error = "Could not update quiz randommatch options!";
return $result;
}
} else {
if (!insert_record("quiz_randommatch", $options)) {
$result->error = "Could not insert quiz randommatch options!";
return $result;
}
}
break;
default:
$result->error = "Unsupported question type ($question->qtype)!";
return $result;
@@ -1281,4 +1431,6 @@ function quiz_save_question_options($question) {
}
?>
+12 -3
View File
@@ -1,4 +1,4 @@
<FORM name="theform" method="post" action="question.php">
<FORM name="theform" method="post" <?=$onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
@@ -21,9 +21,13 @@
formerr($err["questiontext"]);
echo "<BR \>";
}
print_textarea($usehtmleditor, 15, 60, 595, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle");
} else {
helpbutton("text", get_string("helptext"), "moodle");
}
?>
<textarea name="questiontext" rows=5 cols=50 wrap="virtual"><? p($question->questiontext)?></textarea>
<? helpbutton("text", get_string("helptext")); ?>
</TD>
</TR>
<TR valign=top>
@@ -185,3 +189,8 @@
</CENTER>
</FORM>
<?
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

+19
View File
@@ -185,6 +185,14 @@
$question->image = "";
}
// Set up some Richtext editing if necessary
if ($usehtmleditor = can_use_richtext_editor()) {
$defaultformat = FORMAT_HTML;
$onsubmit = "onsubmit=\"copyrichtext(theform.questiontext);\"";
} else {
$defaultformat = FORMAT_MOODLE;
$onsubmit = "";
}
switch ($qtype) {
case SHORTANSWER:
@@ -265,6 +273,17 @@
print_continue("edit.php");
break;
case RANDOMMATCH:
if (!empty($question->id)) {
$options = get_record("quiz_randommatch", "question", $question->id);
} else {
$options->choose = "";
}
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
print_heading_with_help(get_string("editingrandommatch", "quiz"), "randommatch", "quiz");
require("randommatch.html");
break;
default:
error("Invalid question type");
break;
+74
View File
@@ -0,0 +1,74 @@
<FORM name="theform" method="post" <?=$onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
<TD align=right><P><B><? print_string("category", "quiz") ?>:</B></P></TD>
<TD>
<?PHP echo $categories[$question->category]; ?>
<input type="hidden" name="category" value="<?PHP echo "$question->category"; ?>">
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><? print_string("questionname", "quiz") ?>:</B></P></TD>
<TD>
<?PHP
if (empty($question->name)) {
$question->name = get_string("randommatch", "quiz");
}
?>
<INPUT type="text" name="name" size=40 value="<? p($question->name) ?>">
<? if (isset($err["name"])) formerr($err["name"]); ?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><? print_string("introduction", "quiz") ?>:</B></P></TD>
<TD>
<? if (isset($err["questiontext"])) {
formerr($err["questiontext"]);
echo "<BR \>";
}
if (empty($question->questiontext)) {
$question->questiontext = get_string("randommatchintro", "quiz");
}
print_textarea($usehtmleditor, 15, 60, 595, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle");
} else {
helpbutton("text", get_string("helptext"), "moodle");
}
?>
</TD>
</TR>
<TR valign=top>
<TD align=right><P><B><? print_string("randommatchnumber", "quiz") ?>:</B></P></TD>
<TD>
<?
if ($numberavailable < 2) {
echo "ERROR";
} else if ($numberavailable < 6) {
$maxrandom = $numberavailable;
} else {
$maxrandom = 6;
}
for ($i=2;$i<=$maxrandom;$i++) {
$menu[$i] = $i;
}
choose_from_menu($menu, "choose", "$options->choose", "");
unset($menu);
?>
</TD>
</TR>
</TABLE>
<INPUT type="hidden" name=id value="<? p($question->id) ?>">
<INPUT type="hidden" name=qtype value="<? p($question->qtype) ?>">
<INPUT type="submit" value="<? print_string("savechanges") ?>">
</CENTER>
</FORM>
<?
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>
+12 -3
View File
@@ -1,4 +1,4 @@
<FORM name="theform" method="post" action="question.php">
<FORM name="theform" method="post" <?=$onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
@@ -21,9 +21,13 @@
formerr($err["questiontext"]);
echo "<BR \>";
}
print_textarea($usehtmleditor, 15, 60, 595, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle");
} else {
helpbutton("text", get_string("helptext"), "moodle");
}
?>
<textarea name="questiontext" rows=6 cols=50 wrap="virtual"><? p($question->questiontext)?></textarea>
<? helpbutton("text", get_string("helptext")); ?>
</TD>
</TR>
<TR valign=top>
@@ -164,3 +168,8 @@
</CENTER>
</FORM>
<?
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>
+12 -3
View File
@@ -1,4 +1,4 @@
<FORM name="theform" method="post" action="question.php">
<FORM name="theform" method="post" <?=$onsubmit ?> action="question.php">
<CENTER>
<TABLE cellpadding=5>
<TR valign=top>
@@ -21,9 +21,13 @@
formerr($err["questiontext"]);
echo "<BR \>";
}
print_textarea($usehtmleditor, 15, 60, 595, 300, "questiontext", $question->questiontext);
if ($usehtmleditor) {
helpbutton("richtext", get_string("helprichtext"), "moodle");
} else {
helpbutton("text", get_string("helptext"), "moodle");
}
?>
<textarea name="questiontext" rows=6 cols=50 wrap="virtual"><? p($question->questiontext)?></textarea>
<? helpbutton("text", get_string("helptext")); ?>
</TD>
</TR>
<TR valign=top>
@@ -67,3 +71,8 @@
</CENTER>
</FORM>
<?
if ($usehtmleditor) {
print_richedit_javascript("theform", "questiontext", "no");
}
?>
+1 -1
View File
@@ -5,7 +5,7 @@
// This fragment is called by moodle_needs_upgrading() and /admin/index.php
////////////////////////////////////////////////////////////////////////////////
$module->version = 2003021600; // The (date) version of this module
$module->version = 2003022303; // The (date) version of this module
$module->cron = 0; // How often should cron check this module (seconds)?
?>