From 95dbc030a88308873fe0f97261bb0c847c364ad7 Mon Sep 17 00:00:00 2001 From: moodler Date: Mon, 24 Feb 2003 10:37:56 +0000 Subject: [PATCH] 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. --- lang/en/help/quiz/randommatch.html | 10 ++ lang/en/quiz.php | 4 + mod/quiz/attempt.php | 28 +++-- mod/quiz/db/mysql.php | 9 ++ mod/quiz/db/mysql.sql | 11 ++ mod/quiz/edit.php | 2 +- mod/quiz/format/default.php | 15 --- mod/quiz/format/missingword.php | 73 +++++++----- mod/quiz/lib.php | 174 +++++++++++++++++++++++++++-- mod/quiz/multichoice.html | 15 ++- mod/quiz/pix/rm.gif | Bin 0 -> 176 bytes mod/quiz/question.php | 19 ++++ mod/quiz/randommatch.html | 74 ++++++++++++ mod/quiz/shortanswer.html | 15 ++- mod/quiz/truefalse.html | 15 ++- mod/quiz/version.php | 2 +- 16 files changed, 389 insertions(+), 77 deletions(-) create mode 100644 lang/en/help/quiz/randommatch.html create mode 100755 mod/quiz/pix/rm.gif create mode 100644 mod/quiz/randommatch.html diff --git a/lang/en/help/quiz/randommatch.html b/lang/en/help/quiz/randommatch.html new file mode 100644 index 0000000000..23f8efcb3e --- /dev/null +++ b/lang/en/help/quiz/randommatch.html @@ -0,0 +1,10 @@ +

Random Matching questions

+ +

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. + +

The respondent selects the answers that match each question. + +

The questions and answers are randomly drawn from the pool of + "Short Answer" questions in the current category. diff --git a/lang/en/quiz.php b/lang/en/quiz.php index dedd0f12b9..7a8bb211be 100644 --- a/lang/en/quiz.php +++ b/lang/en/quiz.php @@ -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"; diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php index 5fb9188a5b..8e426222aa 100644 --- a/mod/quiz/attempt.php +++ b/mod/quiz/attempt.php @@ -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) } } diff --git a/mod/quiz/db/mysql.php b/mod/quiz/db/mysql.php index ccc5c76651..d9fe4baabb 100644 --- a/mod/quiz/db/mysql.php +++ b/mod/quiz/db/mysql.php @@ -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; } diff --git a/mod/quiz/db/mysql.sql b/mod/quiz/db/mysql.sql index c601b5d906..0e35e92f58 100644 --- a/mod/quiz/db/mysql.sql +++ b/mod/quiz/db/mysql.sql @@ -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` # diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 4962240952..6ad776b50c 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -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; diff --git a/mod/quiz/format/default.php b/mod/quiz/format/default.php index 0f7b70c91e..5dded33387 100644 --- a/mod/quiz/format/default.php +++ b/mod/quiz/format/default.php @@ -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; - } - } ?> diff --git a/mod/quiz/format/missingword.php b/mod/quiz/format/missingword.php index 1dd4a06388..b10cdac0f0 100644 --- a/mod/quiz/format/missingword.php +++ b/mod/quiz/format/missingword.php @@ -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 "

No answers found in $answertext"; - } - return false; + $countanswers = count($answers); + + switch ($countanswers) { + case 0: // invalid question + if ($this->displayerrors) { + echo "

No answers found in $answertext"; + } + return false; + + case 1: + $question->qtype = SHORTANSWER; + + $answer = trim($answers[0]); + if ($answer[0] == "=") { + $answer = substr($answer, 1); + } + $question->answer[] = addslashes($answer); + $question->fraction[] = 1; + $question->feedback[] = ""; + + $question->usecase = 0; // Ignore case + $question->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; } - } ?> diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index ebfd1e5150..8935cbcfea 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -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 ""; break; + case RANDOMMATCH: + echo ""; + break; } echo "\n"; } @@ -504,6 +523,93 @@ function quiz_print_question($number, $questionid, $grade, $courseid, echo "

Random questions not supported yet

"; 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 ""; + foreach ($randomquestions as $key => $randomquestion) { + echo ""; + echo ""; + } + echo "
"; + echo $randomquestion->questiontext; + echo ""; + 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 ""; + choose_from_menu($randomanswers, "q$question->id"."r$randomquestion->id", $responseanswer[$key]); + echo "
"; + } 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 "
"; + break; + default: notify("Error: Unknown question type!"); @@ -693,6 +799,9 @@ function quiz_print_question_list($questionlist, $grades) { echo ""; echo ""; foreach ($order as $qnum) { + if (empty($questions[$qnum])) { + continue; + } $count++; echo "cellcontent\">"; echo ""; @@ -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) { } + + ?> diff --git a/mod/quiz/multichoice.html b/mod/quiz/multichoice.html index 0b95a1409b..5f437700d9 100644 --- a/mod/quiz/multichoice.html +++ b/mod/quiz/multichoice.html @@ -1,4 +1,4 @@ - + action="question.php">
$strorder$strquestionname$strtype$strgrade$stredit
$count
@@ -21,9 +21,13 @@ formerr($err["questiontext"]); echo "
"; } + 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"); + } ?> - - @@ -185,3 +189,8 @@ + diff --git a/mod/quiz/pix/rm.gif b/mod/quiz/pix/rm.gif new file mode 100755 index 0000000000000000000000000000000000000000..861868757d635fda3f6307216add2971b745efb9 GIT binary patch literal 176 zcmV;h08jr%Nk%w1VGsZi0K@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; diff --git a/mod/quiz/randommatch.html b/mod/quiz/randommatch.html new file mode 100644 index 0000000000..e8006cc390 --- /dev/null +++ b/mod/quiz/randommatch.html @@ -0,0 +1,74 @@ +
action="question.php"> +
+
+ + + + + + + + + + + + + + + + +

:

+ category]; ?> + category"; ?>"> +

:

+ name)) { + $question->name = get_string("randommatch", "quiz"); + } + ?> + + +

:

+ "; + } + 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"); + } + ?> +

:

+ choose", ""); + unset($menu); + ?> +
+ + + +"> + + + + diff --git a/mod/quiz/shortanswer.html b/mod/quiz/shortanswer.html index 03fcd78160..8337b5658f 100644 --- a/mod/quiz/shortanswer.html +++ b/mod/quiz/shortanswer.html @@ -1,4 +1,4 @@ -
+ action="question.php">
@@ -21,9 +21,13 @@ formerr($err["questiontext"]); echo "
"; } + 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"); + } ?> - - @@ -164,3 +168,8 @@ + diff --git a/mod/quiz/truefalse.html b/mod/quiz/truefalse.html index cf87b47feb..a8480dc2bf 100644 --- a/mod/quiz/truefalse.html +++ b/mod/quiz/truefalse.html @@ -1,4 +1,4 @@ - + action="question.php">
@@ -21,9 +21,13 @@ formerr($err["questiontext"]); echo "
"; } + 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"); + } ?> - - @@ -67,3 +71,8 @@ + diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 2c47acbdb5..1eb49a5c2e 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -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)? ?> -- 2.39.5