Quiz refactoring is now merged into the main development code!
I've done some basic testing using existing quizzes trying as much as I could think of, and it all worked well! Well done, Henrik! Great job! Still, this should be regarded as unstable at least until more people have had a try. Please test this thing solidly on new and old quizzes.
@ -117,8 +117,8 @@
|
||||
|
||||
$timelimit = $quiz->timelimit * 60;
|
||||
|
||||
$unattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
|
||||
if($timelimit > 0) {
|
||||
$unattempt = quiz_get_user_attempt_unfinished($quiz->id, $USER->id);
|
||||
$timestart = $unattempt->timestart;
|
||||
if($timestart) {
|
||||
$timesincestart = time() - $timestart;
|
||||
@ -153,28 +153,16 @@
|
||||
error("No questions found!");
|
||||
}
|
||||
|
||||
foreach ($rawanswers as $key => $value) { // Parse input for question -> answers
|
||||
foreach ($rawanswers as $key => $value) { // Parse input for question->response
|
||||
|
||||
if (ereg('^q([0-9]+)$', $key, $keyregs)) { // It's a real question number, not a coded one
|
||||
$questions[$keyregs[1]]->answer[] = trim($value);
|
||||
|
||||
} else if (ereg('^q([0-9]+)rq([0-9]+)$', $key, $keyregs)) { // Random Question information
|
||||
$questions[$keyregs[1]]->random = $keyregs[2];
|
||||
|
||||
} else if (ereg('^q([0-9]+)a([0-9]+)$', $key, $keyregs)) { // Checkbox style multiple answers
|
||||
$questions[$keyregs[1]]->answer[] = $keyregs[2];
|
||||
|
||||
} 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";
|
||||
if ($postedquestionid = quiz_extract_posted_id($key)) {
|
||||
$questions[$postedquestionid]->response[$key] = trim($value);
|
||||
|
||||
} else if ('shuffleorder' == $key) {
|
||||
$shuffleorder = explode(",", $value); // Actual order questions were given in
|
||||
|
||||
} else { // Useful for debugging new question types. Must be last.
|
||||
error("Answer received for non-existent question ($key -> $value)");
|
||||
error("Unrecognizable input has been posted ($key -> $value)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,7 +172,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
|
||||
/// Retrieve ->maxgrade for all questions
|
||||
If (!($grades = quiz_get_question_grades($quiz->id, $quiz->questions))) {
|
||||
$grades = array();
|
||||
}
|
||||
foreach ($grades as $qid => $grade) {
|
||||
$questions[$qid]->maxgrade = $grade->grade;
|
||||
}
|
||||
|
||||
if (!$result = quiz_grade_responses($quiz, $questions)) {
|
||||
error("Could not grade your quiz attempt!");
|
||||
}
|
||||
|
||||
@ -213,7 +209,7 @@
|
||||
|
||||
if ($quiz->feedback) {
|
||||
$quiz->shuffleanswers = false; // Never shuffle answers in feedback
|
||||
quiz_print_quiz_questions($quiz, $result, $questions, $shuffleorder);
|
||||
quiz_print_quiz_questions($quiz, $questions, $result, $shuffleorder);
|
||||
print_continue("view.php?id=$cm->id");
|
||||
}
|
||||
|
||||
@ -233,13 +229,14 @@
|
||||
|
||||
/// Actually seeing the questions marks the start of an attempt
|
||||
|
||||
if (!$unfinished = quiz_get_user_attempt_unfinished($quiz->id, $USER->id)) {
|
||||
if ($newattemptid = quiz_start_attempt($quiz->id, $USER->id, $attemptnumber)) {
|
||||
add_to_log($course->id, "quiz", "attempt",
|
||||
"review.php?id=$cm->id&attempt=$newattemptid", "$quiz->id", $cm->id);
|
||||
} else {
|
||||
error("Sorry! Could not start the quiz (could not save starting time)");
|
||||
}
|
||||
if (isset($unattempt) && $unattempt) {
|
||||
$attempt = $unattempt;
|
||||
|
||||
} else if ($attempt = quiz_start_attempt($quiz->id, $USER->id, $attemptnumber)) {
|
||||
add_to_log($course->id, "quiz", "attempt",
|
||||
"review.php?id=$cm->id&attempt=$attempt->id", "$quiz->id", $cm->id);
|
||||
} else {
|
||||
error("Sorry! Could not start the quiz (could not save starting time)");
|
||||
}
|
||||
|
||||
/// First print the headings and so on
|
||||
@ -266,58 +263,32 @@
|
||||
|
||||
echo "<br />";
|
||||
|
||||
$result = NULL; // Default
|
||||
$questions = NULL; // Default
|
||||
if ($quiz->attemptonlast && !empty($attempts)) {
|
||||
$latestfinishedattempt->attempt = 0;
|
||||
foreach ($attempts as $attempt) {
|
||||
if ($attempt->timefinish
|
||||
&& $attempt->attempt > $latestfinishedattempt->attempt)
|
||||
{
|
||||
$latestfinishedattempt = $attempt;
|
||||
}
|
||||
}
|
||||
if ($latestfinishedattempt->attempt > 0
|
||||
and $questions =
|
||||
quiz_get_attempt_responses($latestfinishedattempt))
|
||||
{
|
||||
// An previous attempt to continue on is found:
|
||||
quiz_remove_unwanted_questions($questions, $quiz); // In case the quiz has been changed
|
||||
|
||||
if (!($result = quiz_grade_attempt_results($quiz, $questions))) {
|
||||
// No results, reset to defaults:
|
||||
$questions = NULL;
|
||||
$result = NULL;
|
||||
|
||||
} else {
|
||||
// We're on, latest attempt responses are to be included.
|
||||
// In order to have this accomplished by
|
||||
// the method quiz_print_quiz_questions we need to
|
||||
// temporarilly change some of the $quiz attributes
|
||||
// and remove some of the information from result.
|
||||
|
||||
$quiz->correctanswers = false; // Not a good idea to show them, huh?
|
||||
$result->feedback = array(); // Not to be printed
|
||||
$result->attemptbuildsonthelast = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// No latest attempt, or latest attempt was empty - Reset to defaults
|
||||
$questions = NULL;
|
||||
}
|
||||
$questions = quiz_get_attempt_questions($quiz, $attempt, true);
|
||||
if ($quiz->attemptonlast && $attemptnumber >= 2 and
|
||||
$quiz->attempts == 0 || !unattempt) {
|
||||
// There are unlimited attempts or it is a new attempt.
|
||||
// As the attempt also builds on the last, we can here
|
||||
// have the student see the scores of the pre-entered
|
||||
// responses that we here will have graded:
|
||||
$result = quiz_grade_responses($quiz, $questions);
|
||||
$result->attemptbuildsonthelast = true;
|
||||
} else {
|
||||
$result = NULL;
|
||||
}
|
||||
if (! quiz_print_quiz_questions($quiz, $result, $questions)) {
|
||||
|
||||
// We do not show feedback or correct answers during an attempt:
|
||||
$quiz->feedback = $quiz->correctanswers = false;
|
||||
|
||||
if (!quiz_print_quiz_questions($quiz, $questions, $result)) {
|
||||
print_continue("view.php?id=$cm->id");
|
||||
}
|
||||
|
||||
/// BEGIN EDIT if quiz is available and time limit is set
|
||||
/// include floating timer.
|
||||
/// If quiz is available and time limit is set include floating timer.
|
||||
|
||||
if($available and $timelimit > 0) {
|
||||
if ($available and $timelimit > 0) {
|
||||
require('jstimer.php');
|
||||
}
|
||||
/// END EDIT
|
||||
/// Finish the page
|
||||
|
||||
print_footer($course);
|
||||
|
||||
?>
|
||||
|
@ -59,7 +59,9 @@ class quiz_default_format {
|
||||
|
||||
// Now to save all the answers and type-specific options
|
||||
|
||||
$result = quiz_save_question_options($question);
|
||||
global $QUIZ_QTYPES;
|
||||
$result = $QUIZ_QTYPES[$question->qtype]
|
||||
->save_question_options($question);
|
||||
|
||||
if (!empty($result->error)) {
|
||||
notify($result->error);
|
||||
|
@ -9,135 +9,6 @@
|
||||
|
||||
// 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) {
|
||||
@ -147,7 +18,9 @@ class quiz_file_format extends quiz_default_format {
|
||||
/// multianswer import
|
||||
|
||||
$questions= array();
|
||||
$thequestion= extractMultiAnswerQuestion(addslashes(implode('',$lines)));
|
||||
$thequestion= quiz_qtype_multianswer_extract_question
|
||||
(addslashes(implode('',$lines)));
|
||||
$thequestion->qtype = MULTIANSWER;
|
||||
|
||||
if (!empty($thequestion)) {
|
||||
$thequestion->name = $lines[0];
|
||||
|
2097
mod/quiz/lib.php
@ -98,66 +98,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
if ($form = data_submitted()) {
|
||||
|
||||
// First, save the basic question itself
|
||||
|
||||
$question->name = $form->name;
|
||||
$question->questiontext = $form->questiontext;
|
||||
$question->questiontextformat = $form->questiontextformat;
|
||||
|
||||
if (empty($form->image)) {
|
||||
$question->image = "";
|
||||
} else {
|
||||
$question->image = $form->image;
|
||||
}
|
||||
|
||||
if (isset($form->defaultgrade)) {
|
||||
$question->defaultgrade = $form->defaultgrade;
|
||||
}
|
||||
|
||||
if ($err = formcheck($question)) {
|
||||
notify(get_string("someerrorswerefound"));
|
||||
|
||||
} else {
|
||||
|
||||
if (!empty($question->id)) { // Question already exists
|
||||
$question->version ++; // Update version number of question
|
||||
if (!update_record("quiz_questions", $question)) {
|
||||
error("Could not update question!");
|
||||
}
|
||||
} else { // Question is a new one
|
||||
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
|
||||
$question->version = 1;
|
||||
if (!$question->id = insert_record("quiz_questions", $question)) {
|
||||
error("Could not insert new question!");
|
||||
}
|
||||
}
|
||||
|
||||
// Now to save all the answers and type-specific options
|
||||
|
||||
$form->id = $question->id;
|
||||
$form->qtype = $question->qtype;
|
||||
$form->category = $question->category;
|
||||
|
||||
$result = quiz_save_question_options($form);
|
||||
|
||||
if (!empty($result->error)) {
|
||||
error($result->error);
|
||||
}
|
||||
|
||||
if (!empty($result->notice)) {
|
||||
notice($result->notice, "question.php?id=$question->id");
|
||||
}
|
||||
|
||||
if (!empty($result->noticeyesno)) {
|
||||
notice_yesno($result->noticeyesno, "question.php?id=$question->id", "edit.php");
|
||||
print_footer($course);
|
||||
exit;
|
||||
}
|
||||
|
||||
redirect("edit.php");
|
||||
}
|
||||
if ($form = data_submitted()) {
|
||||
$question = $QUIZ_QTYPES[$qtype]->save_question($question,
|
||||
$form, $course);
|
||||
}
|
||||
|
||||
$grades = array(1,0.9,0.8,0.75,0.70,0.66666,0.60,0.50,0.40,0.33333,0.30,0.25,0.20,0.16666,0.10,0.05,0);
|
||||
@ -210,160 +153,7 @@
|
||||
$onsubmit = "";
|
||||
}
|
||||
|
||||
switch ($qtype) {
|
||||
case SHORTANSWER:
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_shortanswer", "question", $question->id);
|
||||
} else {
|
||||
$options->usecase = 0;
|
||||
}
|
||||
if (!empty($options->answers)) {
|
||||
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
|
||||
}
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$answers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingshortanswer", "quiz"), "shortanswer", "quiz");
|
||||
require("shortanswer.html");
|
||||
break;
|
||||
|
||||
case TRUEFALSE:
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_truefalse", "question", "$question->id");
|
||||
}
|
||||
if (!empty($options->trueanswer)) {
|
||||
$true = get_record("quiz_answers", "id", $options->trueanswer);
|
||||
} else {
|
||||
$true->fraction = 1;
|
||||
$true->feedback = "";
|
||||
}
|
||||
if (!empty($options->falseanswer)) {
|
||||
$false = get_record("quiz_answers", "id", "$options->falseanswer");
|
||||
} else {
|
||||
$false->fraction = 0;
|
||||
$false->feedback = "";
|
||||
}
|
||||
|
||||
if ($true->fraction > $false->fraction) {
|
||||
$question->answer = 1;
|
||||
} else {
|
||||
$question->answer = 0;
|
||||
}
|
||||
|
||||
print_heading_with_help(get_string("editingtruefalse", "quiz"), "truefalse", "quiz");
|
||||
require("truefalse.html");
|
||||
break;
|
||||
|
||||
case MULTICHOICE:
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_multichoice", "question", $question->id);
|
||||
} else {
|
||||
$options->single = 1;
|
||||
}
|
||||
if (!empty($options->answers)) {
|
||||
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
|
||||
}
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$answers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingmultichoice", "quiz"), "multichoice", "quiz");
|
||||
require("multichoice.html");
|
||||
break;
|
||||
|
||||
case MATCH:
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_match", "question", $question->id);
|
||||
if (!empty($options->subquestions)) {
|
||||
$oldsubquestions = get_records_list("quiz_match_sub", "id", $options->subquestions);
|
||||
}
|
||||
}
|
||||
if (empty($subquestions) and empty($subanswers)) {
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$subquestions[] = ""; // Make question slots, default as blank
|
||||
$subanswers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($oldsubquestions)) {
|
||||
$i=0;
|
||||
foreach ($oldsubquestions as $oldsubquestion) {
|
||||
$subquestions[$i] = $oldsubquestion->questiontext; // insert questions into slots
|
||||
$subanswers[$i] = $oldsubquestion->answertext; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingmatch", "quiz"), "match", "quiz");
|
||||
require("match.html");
|
||||
break;
|
||||
|
||||
case RANDOMSAMATCH:
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_randomsamatch", "question", $question->id);
|
||||
} else {
|
||||
$options->choose = "";
|
||||
}
|
||||
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
|
||||
print_heading_with_help(get_string("editingrandomsamatch", "quiz"), "randomsamatch", "quiz");
|
||||
require("randomsamatch.html");
|
||||
break;
|
||||
|
||||
case RANDOM:
|
||||
print_heading_with_help(get_string("editingrandom", "quiz"), "random", "quiz");
|
||||
require("random.html");
|
||||
break;
|
||||
|
||||
case DESCRIPTION:
|
||||
print_heading_with_help(get_string("editingdescription", "quiz"), "description", "quiz");
|
||||
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
|
||||
if (!empty($question->id)) {
|
||||
$answersraw= quiz_get_answers($question);
|
||||
}
|
||||
$answers= array();
|
||||
for ($i=0; $i<6; $i++) {
|
||||
$answers[$i]->answer = ""; // Make answer slots, default as blank...
|
||||
$answers[$i]->min = "";
|
||||
$answers[$i]->max = "";
|
||||
$answers[$i]->feedback = "";
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz");
|
||||
require("numerical.html");
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
error("Invalid question type");
|
||||
break;
|
||||
}
|
||||
require('questiontypes/'.$QUIZ_QTYPES[$qtype]->name().'/editquestion.php');
|
||||
|
||||
print_footer($course);
|
||||
|
||||
|
80
mod/quiz/questiontypes/description/description.html
Normal file
@ -0,0 +1,80 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="hidden" name=defaultgrade value="0">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
6
mod/quiz/questiontypes/description/editquestion.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
print_heading_with_help(get_string("editingdescription", "quiz"), "description", "quiz");
|
||||
require("description.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/description/icon.gif
Normal file
After Width: | Height: | Size: 117 B |
57
mod/quiz/questiontypes/description/questiontype.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
///////////////////
|
||||
/// DESCRIPTION ///
|
||||
///////////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
|
||||
//
|
||||
// The question type DESCRIPTION is not really a question type
|
||||
// and it therefore often sticks to some kind of odd behaviour
|
||||
//
|
||||
|
||||
class quiz_description_qtype extends quiz_default_questiontype {
|
||||
|
||||
function name() {
|
||||
return 'description';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
/// No options to be saved for this question type:
|
||||
return true;
|
||||
}
|
||||
|
||||
function create_response($question, $nameprefix, $questionsinuse) {
|
||||
/// This question type does never have any responses,
|
||||
/// so do not return any...
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
function print_question($currentnumber, $quiz, $question,
|
||||
$readonly, $resultdetails) {
|
||||
echo '<p align="center">';
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
echo '</p>';
|
||||
return $currentnumber;
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
$result->grade = 0.0;
|
||||
$result->answers = array();
|
||||
$result->correctanswers = array();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[DESCRIPTION]= new quiz_description_qtype();
|
||||
|
||||
?>
|
25
mod/quiz/questiontypes/match/editquestion.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?PHP // $Id$
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_match", "question", $question->id);
|
||||
if (!empty($options->subquestions)) {
|
||||
$oldsubquestions = get_records_list("quiz_match_sub", "id", $options->subquestions);
|
||||
}
|
||||
}
|
||||
if (empty($subquestions) and empty($subanswers)) {
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$subquestions[] = ""; // Make question slots, default as blank
|
||||
$subanswers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($oldsubquestions)) {
|
||||
$i=0;
|
||||
foreach ($oldsubquestions as $oldsubquestion) {
|
||||
$subquestions[$i] = $oldsubquestion->questiontext; // insert questions into slots
|
||||
$subanswers[$i] = $oldsubquestion->answertext; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingmatch", "quiz"), "match", "quiz");
|
||||
require("match.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/match/icon.gif
Normal file
After Width: | Height: | Size: 201 B |
104
mod/quiz/questiontypes/match/match.html
Normal file
@ -0,0 +1,104 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("choices", "quiz") ?></B>:</P></TD>
|
||||
<TD><P><?php print_string("filloutthreequestions", "quiz") ?></P>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<?PHP
|
||||
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
?>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php echo get_string("question", "quiz")." $i"; ?> :</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="subquestions[]" rows=5 cols=50><?php p($subquestions[$i-1]) ?></textarea>
|
||||
<br />
|
||||
<?php echo get_string("matchanswer", "quiz")." $i"; ?>
|
||||
<INPUT type="text" name="subanswers[]" size=50 value="<?php p($subanswers[$i-1]) ?>">
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<?PHP
|
||||
}
|
||||
?>
|
||||
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
247
mod/quiz/questiontypes/match/questiontype.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
/////////////
|
||||
/// MATCH ///
|
||||
/////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
class quiz_match_qtype extends quiz_default_questiontype {
|
||||
|
||||
function name() {
|
||||
return 'match';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
|
||||
if (!$oldsubquestions = get_records("quiz_match_sub", "question", $question->id, "id ASC")) {
|
||||
$oldsubquestions = array();
|
||||
}
|
||||
|
||||
// following hack to check at least three answers exist
|
||||
$answercount = 0;
|
||||
foreach ($question->subquestions as $key=>$questiontext) {
|
||||
$answertext = $question->subanswers[$key];
|
||||
if (!empty($questiontext) and !empty($answertext)) {
|
||||
$answercount++;
|
||||
}
|
||||
}
|
||||
$answercount += count($oldsubquestions);
|
||||
if ($answercount < 3) { // check there are at lest 3 answers for matching type questions
|
||||
$result->notice = get_string("notenoughanswers", "quiz", "3");
|
||||
return $result;
|
||||
}
|
||||
|
||||
$subquestions = array();
|
||||
|
||||
// Insert all the new question+answer pairs
|
||||
foreach ($question->subquestions as $key => $questiontext) {
|
||||
$answertext = $question->subanswers[$key];
|
||||
if (!empty($questiontext) and !empty($answertext)) {
|
||||
if ($subquestion = array_shift($oldsubquestions)) { // Existing answer, so reuse it
|
||||
$subquestion->questiontext = $questiontext;
|
||||
$subquestion->answertext = $answertext;
|
||||
if (!update_record("quiz_match_sub", $subquestion)) {
|
||||
$result->error = "Could not insert quiz match subquestion! (id=$subquestion->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($subquestion);
|
||||
$subquestion->question = $question->id;
|
||||
$subquestion->questiontext = $questiontext;
|
||||
$subquestion->answertext = $answertext;
|
||||
if (!$subquestion->id = insert_record("quiz_match_sub", $subquestion)) {
|
||||
$result->error = "Could not insert quiz match subquestion!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
$subquestions[] = $subquestion->id;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($subquestions) < 3) {
|
||||
$result->noticeyesno = get_string("notenoughsubquestions", "quiz");
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ($options = get_record("quiz_match", "question", $question->id)) {
|
||||
$options->subquestions = implode(",",$subquestions);
|
||||
if (!update_record("quiz_match", $options)) {
|
||||
$result->error = "Could not update quiz match options! (id=$options->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($options);
|
||||
$options->question = $question->id;
|
||||
$options->subquestions = implode(",",$subquestions);
|
||||
if (!insert_record("quiz_match", $options)) {
|
||||
$result->error = "Could not insert quiz match options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function convert_to_response_answer_field($questionresponse) {
|
||||
/// This method, together with extract_response, should be
|
||||
/// obsolete as soon as we get a better response storage
|
||||
|
||||
$delimiter = '';
|
||||
$responseanswerfield = '';
|
||||
foreach ($questionresponse as $key => $value) {
|
||||
if ($matchid = $this->extract_response_id($key)) {
|
||||
$responseanswerfield .= "$delimiter$matchid-$value";
|
||||
$delimiter = ',';
|
||||
} else {
|
||||
notify("Error: Illegal match key $key detected");
|
||||
}
|
||||
}
|
||||
return $responseanswerfield;
|
||||
}
|
||||
|
||||
function extract_response($rawresponse, $nameprefix) {
|
||||
if (!($options = get_record("quiz_match",
|
||||
"question", $rawresponse->question))) {
|
||||
notify("Error: Missing question options!");
|
||||
return array();
|
||||
}
|
||||
$subids = explode(',', $options->subquestions);
|
||||
foreach ($subids as $subid) {
|
||||
$response[$nameprefix.$subid] =
|
||||
ereg("(^|,)$subid-([^,]+)", $rawresponse->answer, $regs)
|
||||
? $regs[2]
|
||||
: '';
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
|
||||
// Print question text and possible image
|
||||
if (!empty($question->questiontext)) {
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
}
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
// It so happens to be that $correctanswers for this question type also
|
||||
// contains the subqustions, which we need to make sure we have:
|
||||
if (empty($correctanswers)) {
|
||||
$options = get_record('quiz_match', 'question', $question->id)
|
||||
and $subquestions = get_records_list('quiz_match_sub', 'id',
|
||||
$options->subquestions);
|
||||
} else {
|
||||
$subquestions = $correctanswers;
|
||||
}
|
||||
|
||||
/// Check whether everything turned out alright:
|
||||
if (empty($subquestions)) {
|
||||
notify("Error: Missing subquestions for this question!");
|
||||
|
||||
} else {
|
||||
/// Everything is fine -
|
||||
/// Set up $subquestions and $answers and do the shuffling:
|
||||
|
||||
if ($quiz->shuffleanswers) {
|
||||
$subquestions = draw_rand_array($subquestions,
|
||||
count($subquestions));
|
||||
}
|
||||
foreach ($subquestions as $key => $subquestion) {
|
||||
unset($answers[$key]);
|
||||
$answers[$subquestion->id] = $subquestion->answertext;
|
||||
}
|
||||
$answers = draw_rand_array($answers, count($answers));
|
||||
}
|
||||
|
||||
///// Ptint the input controls //////
|
||||
|
||||
echo '<table border="0" cellpadding="10" align="right">';
|
||||
foreach ($subquestions as $subquestion) {
|
||||
|
||||
/// Subquestion text:
|
||||
echo '<tr><td align="left" valign="top">';
|
||||
echo $subquestion->questiontext;
|
||||
echo '</td>';
|
||||
|
||||
/// Drop-down list:
|
||||
$menuname = $nameprefix.$subquestion->id;
|
||||
$response = isset($question->response[$menuname])
|
||||
? $question->response[$menuname] : '0';
|
||||
if (isset($correctanswers[$menuname])
|
||||
&& $correctanswers[$menuname]->id
|
||||
== $response) {
|
||||
$class = ' class="highlight" ';
|
||||
} else {
|
||||
$class = '';
|
||||
}
|
||||
echo "<td align=\"right\" valign=\"top\" $class>";
|
||||
choose_from_menu($answers, $menuname, $response);
|
||||
if ($quiz->feedback && isset($answers[$menuname])
|
||||
&& $answers[$menuname]->feedback) {
|
||||
quiz_print_comment($answers[$menuname]->feedback);
|
||||
}
|
||||
echo '</td></tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
/// This question type does not use the table quiz_answers
|
||||
/// but we will take some measures to emulate that record anyway.
|
||||
|
||||
$result->grade = 0.0;
|
||||
$result->answers = array();
|
||||
$result->correctanswers = array();
|
||||
|
||||
if (!($options = get_record('quiz_match', 'question', $question->id)
|
||||
and $subquestions = get_records_list('quiz_match_sub',
|
||||
'id', $options->subquestions))) {
|
||||
notify("Error: Cannot find match options and subquestions
|
||||
for question $question->id");
|
||||
return $result;
|
||||
}
|
||||
|
||||
$fraction = 1.0 / count($subquestions);
|
||||
|
||||
/// Populate correctanswers arrays:
|
||||
foreach ($subquestions as $subquestion) {
|
||||
$subquestion->fraction = $fraction;
|
||||
$subquestion->answer = $subquestion->answertext;
|
||||
$subquestion->feedback = '';
|
||||
$result->correctanswers[$nameprefix.$subquestion->id] =
|
||||
$subquestion;
|
||||
}
|
||||
|
||||
foreach ($question->response as $responsekey => $answerid) {
|
||||
|
||||
if ($answerid and $answer =
|
||||
$result->correctanswers[$nameprefix.$answerid]) {
|
||||
|
||||
if ($result->correctanswers[$responsekey]->answer
|
||||
== $answer->answer) {
|
||||
|
||||
/// The response was correct!
|
||||
$result->answers[$responsekey] =
|
||||
$result->correctanswers[$responsekey];
|
||||
$result->grade += $fraction;
|
||||
|
||||
} else {
|
||||
/// The response was incorrect:
|
||||
$answer->fraction = 0.0;
|
||||
$result->answers[$responsekey] = $answer;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[MATCH]= new quiz_match_qtype();
|
||||
|
||||
?>
|
46
mod/quiz/questiontypes/multianswer/editquestion.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?PHP // $Id$
|
||||
if ($question->questiontext and $question->id) {
|
||||
$answers = quiz_get_answers($question);
|
||||
|
||||
foreach ($answers as $multianswer) {
|
||||
$parsableanswerdef = '{' . $multianswer->norm . ':';
|
||||
switch ($multianswer->answertype) {
|
||||
case MULTICHOICE:
|
||||
$parsableanswerdef .= 'MULTICHOICE:';
|
||||
break;
|
||||
case SHORTANSWER:
|
||||
$parsableanswerdef .= 'SHORTANSWER:';
|
||||
break;
|
||||
case NUMERICAL:
|
||||
$parsableanswerdef .= 'NUMERICAL:';
|
||||
break;
|
||||
default:
|
||||
error("answertype $multianswer->answertype not recognized");
|
||||
}
|
||||
$separator= '';
|
||||
foreach ($multianswer->subanswers as $subanswer) {
|
||||
$parsableanswerdef .= $separator
|
||||
. '%' . round(100*$subanswer->fraction) . '%';
|
||||
$parsableanswerdef .= $subanswer->answer;
|
||||
if (isset($subanswer->min) && isset($subanswer->max)
|
||||
and $subanswer->min || $subanswer->max) {
|
||||
// Special for numerical answers:
|
||||
$errormargin = $subanswer->answer - $subanswer->min;
|
||||
$parsableanswerdef .= ":$errormargin";
|
||||
}
|
||||
if ($subanswer->feedback) {
|
||||
$parsableanswerdef .= "#$subanswer->feedback";
|
||||
}
|
||||
$separator = '~';
|
||||
}
|
||||
$parsableanswerdef .= '}';
|
||||
$question->questiontext = str_replace
|
||||
("{#$multianswer->positionkey}", $parsableanswerdef,
|
||||
$question->questiontext);
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string('editingmultianswer', 'quiz'),
|
||||
'multianswer', 'quiz');
|
||||
require('multianswer.html');
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/multianswer/icon.gif
Normal file
After Width: | Height: | Size: 183 B |
114
mod/quiz/questiontypes/multianswer/multianswer.html
Normal file
@ -0,0 +1,114 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
|
||||
<CENTER>
|
||||
|
||||
<TABLE cellpadding=5>
|
||||
|
||||
<TR valign=top>
|
||||
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
|
||||
<TD>
|
||||
|
||||
<?php quiz_category_select_menu($course->id, true, true ); ?>
|
||||
|
||||
</TD>
|
||||
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
|
||||
<TD>
|
||||
|
||||
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
|
||||
</TD>
|
||||
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
|
||||
<TD align=right><P><B><?php print_string("question", "quiz") ?>:</B></P></TD>
|
||||
|
||||
<TD>
|
||||
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
|
||||
formerr($err["questiontext"]);
|
||||
|
||||
echo "<BR />";
|
||||
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 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><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
|
||||
<TD>
|
||||
|
||||
<?php if (empty($images)) {
|
||||
|
||||
print_string("noimagesyet");
|
||||
|
||||
} else {
|
||||
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
</TD>
|
||||
|
||||
</TR>
|
||||
|
||||
</TABLE>
|
||||
|
||||
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
|
||||
<INPUT type="hidden" name=defaultgrade value="<?php p($question->defaultgrade) ?>">
|
||||
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
|
||||
|
||||
</CENTER>
|
||||
|
||||
</FORM>
|
||||
|
||||
<?php
|
||||
|
||||
if ($usehtmleditor) {
|
||||
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
|
623
mod/quiz/questiontypes/multianswer/questiontype.php
Normal file
@ -0,0 +1,623 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
///////////////////
|
||||
/// MULTIANSWER /// (Embedded - cloze)
|
||||
///////////////////
|
||||
|
||||
///
|
||||
/// The multianswer question type is special in that it
|
||||
/// depends on a few other question types, i.e.
|
||||
/// MULTICHOICE, SHORTANSWER and NUMERICAL.
|
||||
/// These question types have got a few special features that
|
||||
/// makes them useable by the MULTIANSWER question type
|
||||
///
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
class quiz_embedded_cloze_qtype extends quiz_default_questiontype {
|
||||
|
||||
function get_answers($question) {
|
||||
/// The returned answers includes subanswers...
|
||||
// As this question type embedds some other question types,
|
||||
// it is necessary to have access to those:
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
$answers = array();
|
||||
|
||||
$virtualquestion->id = $question->id;
|
||||
|
||||
if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
|
||||
foreach ($multianswers as $multianswer) {
|
||||
$virtualquestion->qtype = $multianswer->answertype;
|
||||
// Call to other question type for subanswers
|
||||
$addedcondition = " AND a.id IN ($multianswer->answers) ";
|
||||
$multianswer->subanswers =
|
||||
$QUIZ_QTYPES[$multianswer->answertype]
|
||||
->get_answers($virtualquestion, $addedcondition);
|
||||
$answers[] = $multianswer;
|
||||
}
|
||||
}
|
||||
return $answers;
|
||||
}
|
||||
|
||||
function name() {
|
||||
return 'multianswer';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
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_qtype_multianswer_save_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_qtype_multianswer_save_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function save_question($authorizedquestion, $form, $course) {
|
||||
|
||||
$question = quiz_qtype_multianswer_extract_question
|
||||
($form->questiontext);
|
||||
$question->id = $authorizedquestion->id;
|
||||
$question->qtype = $authorizedquestion->qtype;
|
||||
$question->category = $authorizedquestion->category;
|
||||
|
||||
$question->name = $form->name;
|
||||
if (empty($form->image)) {
|
||||
$question->image = "";
|
||||
} else {
|
||||
$question->image = $form->image;
|
||||
}
|
||||
|
||||
// Formcheck
|
||||
$err = array();
|
||||
if (empty($question->name)) {
|
||||
$err["name"] = get_string("missingname", "quiz");
|
||||
}
|
||||
if (empty($question->questiontext)) {
|
||||
$err["questiontext"] = get_string("missingquestiontext", "quiz");
|
||||
}
|
||||
if ($err) { // Formcheck failed
|
||||
notify(get_string("someerrorswerefound"));
|
||||
|
||||
} else {
|
||||
|
||||
if (!empty($question->id)) { // Question already exists
|
||||
if (!update_record("quiz_questions", $question)) {
|
||||
error("Could not update question!");
|
||||
}
|
||||
} else { // Question is a new one
|
||||
$question->stamp = make_unique_id_code(); // Set the unique code (not to be changed)
|
||||
if (!$question->id = insert_record("quiz_questions", $question)) {
|
||||
error("Could not insert new question!");
|
||||
}
|
||||
}
|
||||
|
||||
// Now to save all the answers and type-specific options
|
||||
$result = $this->save_question_options($question);
|
||||
|
||||
if (!empty($result->error)) {
|
||||
error($result->error);
|
||||
}
|
||||
|
||||
if (!empty($result->notice)) {
|
||||
notice_yesno($result->notice, "question.php?id=$question->id", "edit.php");
|
||||
print_footer($course);
|
||||
exit;
|
||||
}
|
||||
|
||||
redirect("edit.php");
|
||||
}
|
||||
}
|
||||
|
||||
function convert_to_response_answer_field($questionresponse) {
|
||||
/// This method, together with extract_response, should be
|
||||
/// obsolete as soon as we get a better response storage
|
||||
|
||||
$delimiter = '';
|
||||
$responseanswerfield = '';
|
||||
foreach ($questionresponse as $key => $value) {
|
||||
if ($multianswerid = $this->extract_response_id($key)) {
|
||||
$responseanswerfield .= "$delimiter$multianswerid-$value";
|
||||
$delimiter = ',';
|
||||
} else {
|
||||
notify("Error: Illegal match key $key detected");
|
||||
}
|
||||
}
|
||||
return $responseanswerfield;
|
||||
}
|
||||
|
||||
function extract_response($rawresponse, $nameprefix) {
|
||||
/// A temporary fix for bug #647 has accidently been enforced here
|
||||
/// because of the odd circumstances during the refactoring
|
||||
|
||||
$multianswers = get_records('quiz_multianswers',
|
||||
'question', $rawresponse->question);
|
||||
$response = array();
|
||||
foreach ($multianswers as $maid => $multianswer) {
|
||||
if (ereg("(^|,)$maid-(.*)", $rawresponse->answer, $regs)) {
|
||||
$splits = split(',[0-9]+-', $regs[2], 2);
|
||||
$response[$nameprefix.$maid] = $splits[0];
|
||||
} else {
|
||||
$response[$nameprefix.$maid] = '';
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
global $THEME;
|
||||
|
||||
// For this question type, we better print the image on top:
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
$qtextremaining = format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
|
||||
$strfeedback = get_string('feedback', 'quiz');
|
||||
|
||||
// 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 = $nameprefix.$multianswer->id;
|
||||
$response = isset($question->response[$inputname])
|
||||
? $question->response[$inputname] : '';
|
||||
|
||||
/// Determine style
|
||||
if (!empty($correctanswers) && '' !== $response) {
|
||||
|
||||
if (!isset($answers[$inputname])
|
||||
|| $answers[$inputname]->fraction <= 0.0) {
|
||||
// The response must have been totally wrong:
|
||||
$style = ' style="background-color:red" ';
|
||||
|
||||
} else if ($answers[$inputname]->fraction >= 1.0) {
|
||||
// The response must was correct!!
|
||||
$style = 'style="background-color:lime"';
|
||||
|
||||
} else {
|
||||
// This response did at least give some credit:
|
||||
$style = 'style="background-color:yellow"';
|
||||
}
|
||||
} else {
|
||||
// No colorish feedback is to be used
|
||||
$style = '';
|
||||
}
|
||||
|
||||
// Determine feedback popup if any
|
||||
if ($quiz->feedback && isset($answers[$inputname])
|
||||
&& '' !== $answers[$inputname]->feedback) {
|
||||
$title = str_replace("'", "\\'", $answers[$inputname]->feedback);
|
||||
$popup = " onmouseover=\"return overlib('$title', CAPTION, '$strfeedback', FGCOLOR, '$THEME->cellcontent');\" ".
|
||||
" onmouseout=\"return nd();\" ";
|
||||
} else {
|
||||
$popup = '';
|
||||
}
|
||||
|
||||
// Print the input control
|
||||
switch ($multianswer->answertype) {
|
||||
case SHORTANSWER:
|
||||
case NUMERICAL:
|
||||
echo " <input $style $readonly $popup name=\"$inputname\"
|
||||
type=\"text\" value=\"$response\" size=\"12\" /> ";
|
||||
break;
|
||||
case MULTICHOICE:
|
||||
$outputoptions = '<option></option>'; // Default empty option
|
||||
$mcanswers = get_records_list("quiz_answers", "id", $multianswer->answers);
|
||||
foreach ($mcanswers as $mcanswer) {
|
||||
$selected = $response == $mcanswer->id
|
||||
? ' selected="selected" ' : '';
|
||||
$outputoptions .= "<option value=\"$mcanswer->id\" $selected>$mcanswer->answer</option>";
|
||||
}
|
||||
echo "<select $popup $style name=\"$inputname\" $readonly>";
|
||||
echo $outputoptions;
|
||||
echo '</select>';
|
||||
break;
|
||||
default:
|
||||
error("Unable to recognized answertype $answer->answertype");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Print the final piece of question text:
|
||||
echo $qtextremaining;
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
$result->grade = 0.0;
|
||||
$result->answers = array();
|
||||
$result->correctanswers = array();
|
||||
|
||||
$multianswers = get_records('quiz_multianswers',
|
||||
'question', $question->id);
|
||||
// Default settings:
|
||||
$subquestion->id = $question->id;
|
||||
$normsum = 0;
|
||||
|
||||
// Grade each multianswer
|
||||
foreach ($multianswers as $multianswer) {
|
||||
$name = $nameprefix.$multianswer->id;
|
||||
$subquestion->response[$nameprefix] =
|
||||
isset($question->response[$name])
|
||||
? $question->response[$name] : '';
|
||||
|
||||
$subresult = $QUIZ_QTYPES[$multianswer->answertype]
|
||||
->grade_response($subquestion, $nameprefix,
|
||||
" AND a.id IN ($multianswer->answers) ");
|
||||
|
||||
// Summarize subquestion results:
|
||||
|
||||
if (isset($subresult->answers[$nameprefix])) {
|
||||
|
||||
/// Answer was found:
|
||||
$result->answers[$name] = $subresult->answers[$nameprefix];
|
||||
|
||||
if ($result->answers[$name]->fraction >= 1.0) {
|
||||
// This is also the correct answer:
|
||||
$result->correctanswers[$name] = $result->answers[$name];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($result->correctanswers[$name])) {
|
||||
// Pick the first correctanswer:
|
||||
foreach ($subresult->correctanswers as $correctanswer) {
|
||||
$result->correctanswers[$name] = $correctanswer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$result->grade += $multianswer->norm * $subresult->grade;
|
||||
$normsum += $multianswer->norm;
|
||||
}
|
||||
$result->grade /= $normsum;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[MULTIANSWER]= new quiz_embedded_cloze_qtype();
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
//// ADDITIONAL FUNCTIONS
|
||||
//// The functions below deal exclusivly with editing
|
||||
//// of questions with question type MULTIANSWER.
|
||||
//// Therefore they are kept in this file.
|
||||
//// They are not in the class as they are not
|
||||
//// likely to be subject for overriding.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
function quiz_qtype_multianswer_extract_question($text) {
|
||||
|
||||
////////////////////////////////////////////////
|
||||
//// Define some constants first. It is not the
|
||||
//// pattern commonly used in quiz/questiontypes.
|
||||
//// The reason is that it has been moved here from
|
||||
//// quiz/format/multianswer/format.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);
|
||||
|
||||
////////////////////////////////////////
|
||||
//// Start of the actual function
|
||||
////////////////////////////////////////
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
function quiz_qtype_multianswer_save_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', $altobsolete->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;
|
||||
}
|
||||
|
||||
?>
|
23
mod/quiz/questiontypes/multichoice/editquestion.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?PHP // $Id$
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_multichoice", "question", $question->id);
|
||||
} else {
|
||||
$options->single = 1;
|
||||
}
|
||||
if (!empty($options->answers)) {
|
||||
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
|
||||
}
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$answers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingmultichoice", "quiz"), "multichoice", "quiz");
|
||||
require("multichoice.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/multichoice/icon.gif
Normal file
After Width: | Height: | Size: 214 B |
124
mod/quiz/questiontypes/multichoice/multichoice.html
Normal file
@ -0,0 +1,124 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("answerhowmany", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php
|
||||
$menu[0] = get_string("answersingleno", "quiz");
|
||||
$menu[1] = get_string("answersingleyes", "quiz");
|
||||
choose_from_menu($menu, "single", "$options->single", "");
|
||||
unset($menu);
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("choices", "quiz") ?></B>:</P></TD>
|
||||
<TD><P><?php print_string("fillouttwochoices", "quiz") ?></P>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<?PHP
|
||||
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
?>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php echo get_string("choice", "quiz")." $i"; ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="answer[]" size=50 maxlength=255 value="<?php p($answers[$i-1]->answer) ?>">
|
||||
<?php print_string("grade");
|
||||
echo ": ";
|
||||
choose_from_menu($gradeoptionsfull, "fraction[]", $answers[$i-1]->fraction, ""); ?>
|
||||
<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[$i-1]->feedback) ?></textarea>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD colspan=2> </TD>
|
||||
</TR>
|
||||
|
||||
<?php
|
||||
} /// End of loop, printing answers
|
||||
?>
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
274
mod/quiz/questiontypes/multichoice/questiontype.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
///////////////////
|
||||
/// MULTICHOICE ///
|
||||
///////////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
|
||||
///
|
||||
/// This class contains some special features in order to make the
|
||||
/// question type embeddable within a multianswer (cloze) question
|
||||
///
|
||||
|
||||
class quiz_multichoice_qtype extends quiz_default_questiontype {
|
||||
|
||||
function get_answers($question, $addedcondition= '') {
|
||||
// The added condition is one addition that has been added
|
||||
// to the behaviour of this question type in order to make
|
||||
// it embeddable within a multianswer (embedded cloze) question
|
||||
|
||||
global $CFG;
|
||||
|
||||
// There should be multiple answers
|
||||
return get_records_sql("SELECT a.*, mc.single
|
||||
FROM {$CFG->prefix}quiz_multichoice mc,
|
||||
{$CFG->prefix}quiz_answers a
|
||||
WHERE mc.question = '$question->id'
|
||||
AND mc.question = a.question "
|
||||
. $addedcondition);
|
||||
}
|
||||
|
||||
function name() {
|
||||
return 'multichoice';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
|
||||
if (!$oldanswers = get_records("quiz_answers", "question",
|
||||
$question->id, "id ASC")) {
|
||||
$oldanswers = array();
|
||||
}
|
||||
|
||||
// following hack to check at least two answers exist
|
||||
$answercount = 0;
|
||||
foreach ($question->answer as $key=>$dataanswer) {
|
||||
if ($dataanswer != "") {
|
||||
$answercount++;
|
||||
}
|
||||
}
|
||||
$answercount += count($oldanswers);
|
||||
if ($answercount < 2) { // check there are at lest 2 answers for multiple choice
|
||||
$result->notice = get_string("notenoughanswers", "quiz", "2");
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Insert all the new answers
|
||||
|
||||
$totalfraction = 0;
|
||||
$maxfraction = -1;
|
||||
|
||||
$answers = array();
|
||||
|
||||
foreach ($question->answer as $key => $dataanswer) {
|
||||
if ($dataanswer != "") {
|
||||
if ($answer = array_shift($oldanswers)) { // Existing answer, so reuse it
|
||||
$answer->answer = $dataanswer;
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!update_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not update quiz answer! (id=$answer->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($answer);
|
||||
$answer->answer = $dataanswer;
|
||||
$answer->question = $question->id;
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!$answer->id = insert_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not insert quiz answer! ";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
$answers[] = $answer->id;
|
||||
|
||||
if ($question->fraction[$key] > 0) { // Sanity checks
|
||||
$totalfraction += $question->fraction[$key];
|
||||
}
|
||||
if ($question->fraction[$key] > $maxfraction) {
|
||||
$maxfraction = $question->fraction[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($options = get_record("quiz_multichoice", "question", $question->id)) {
|
||||
$options->answers = implode(",",$answers);
|
||||
$options->single = $question->single;
|
||||
if (!update_record("quiz_multichoice", $options)) {
|
||||
$result->error = "Could not update quiz multichoice options! (id=$options->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($options);
|
||||
$options->question = $question->id;
|
||||
$options->answers = implode(",",$answers);
|
||||
$options->single = $question->single;
|
||||
if (!insert_record("quiz_multichoice", $options)) {
|
||||
$result->error = "Could not insert quiz multichoice options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform sanity checks on fractional grades
|
||||
if ($options->single) {
|
||||
if ($maxfraction != 1) {
|
||||
$maxfraction = $maxfraction * 100;
|
||||
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
$totalfraction = round($totalfraction,2);
|
||||
if ($totalfraction != 1) {
|
||||
$totalfraction = $totalfraction * 100;
|
||||
$result->noticeyesno = get_string("fractionsaddwrong", "quiz", $totalfraction);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function extract_response($rawresponse, $nameprefix) {
|
||||
// Fetch additional details from the database...
|
||||
if (!$options = get_record("quiz_multichoice",
|
||||
"question", $rawresponse->question)) {
|
||||
notify("Error: Missing question options!");
|
||||
}
|
||||
|
||||
if ($options->single) {
|
||||
return array($nameprefix => $rawresponse->answer);
|
||||
|
||||
} else {
|
||||
$response = array();
|
||||
$answerids = explode(',', $options->answers);
|
||||
foreach ($answerids as $answerid) {
|
||||
$response[$nameprefix.$answerid] =
|
||||
ereg("(,|^)$answerid(,|$)", $rawresponse->answer)
|
||||
? $answerid
|
||||
: '';
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
|
||||
// Fetch additional details from the database...
|
||||
if (!$options = get_record("quiz_multichoice", "question", $question->id)) {
|
||||
notify("Error: Missing question options!");
|
||||
}
|
||||
if (!$answers = get_records_list("quiz_answers", "id", $options->answers)) {
|
||||
notify("Error: Missing question answers!");
|
||||
}
|
||||
|
||||
// Print formulation
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
// Print input controls and alternatives
|
||||
echo "<table align=\"right\">";
|
||||
$stranswer = get_string("answer", "quiz");
|
||||
echo "<tr><td valign=\"top\">$stranswer: </td><td>";
|
||||
echo "<table>";
|
||||
$answerids = explode(",", $options->answers);
|
||||
|
||||
if ($quiz->shuffleanswers) {
|
||||
$answerids = swapshuffle($answerids);
|
||||
}
|
||||
|
||||
// Handle the case of unanswered single-choice questions:
|
||||
if ($options->single) {
|
||||
$singleresponse = isset($question->response[$nameprefix])
|
||||
? $question->response[$nameprefix] : '0';
|
||||
}
|
||||
|
||||
foreach ($answerids as $key => $aid) {
|
||||
$answer = $answers[$aid];
|
||||
$qnumchar = chr(ord('a') + $key);
|
||||
|
||||
echo '<tr><td valign="top">';
|
||||
|
||||
if ($options->single) {
|
||||
$type = ' type="radio" ';
|
||||
$name = " name=\"$nameprefix\" ";
|
||||
$checked = $singleresponse == $aid
|
||||
? ' checked="checked" ' : '';
|
||||
} else {
|
||||
$type = ' type="checkbox" ';
|
||||
$name = " name=\"$nameprefix$aid\" ";
|
||||
$checked = !empty($question->response[$nameprefix.$aid])
|
||||
? ' checked="checked" ' : '';
|
||||
}
|
||||
if ($readonly) {
|
||||
$readonly = ' readonly="readonly" disabled="disabled" ';
|
||||
}
|
||||
echo "<input $readonly $name $checked $type value=\"$answer->id\" />";
|
||||
|
||||
echo "</td>";
|
||||
if ($readonly and $quiz->correctanswers || $quiz->feedback
|
||||
and !empty($correctanswers[$nameprefix.$aid])) {
|
||||
echo '<td valign="top" class="highlight">'.format_text("$qnumchar. $answer->answer").'</td>';
|
||||
} else {
|
||||
echo '<td valign="top">'.format_text("$qnumchar. $answer->answer").'</td>';
|
||||
}
|
||||
if ($quiz->feedback) {
|
||||
echo "<td valign=\"top\"> ";
|
||||
if ($checked) { // Simpliest condition to use here
|
||||
quiz_print_comment($answer->feedback);
|
||||
}
|
||||
echo "</td>";
|
||||
}
|
||||
echo "</tr>";
|
||||
}
|
||||
echo "</table>";
|
||||
echo "</td></tr></table>";
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix, $addedanswercondition='') {
|
||||
|
||||
$result->correctanswers = array();
|
||||
$result->answers = array();
|
||||
$result->grade = 0.0;
|
||||
|
||||
$answers = $this->get_answers($question, $addedanswercondition);
|
||||
|
||||
/// Set ->answers[] and ->grade
|
||||
if (!empty($question->response)) {
|
||||
foreach ($question->response as $name => $response) {
|
||||
if (isset($answers[$response])) {
|
||||
$result->answers[$name] = $answers[$response];
|
||||
$result->grade += $answers[$response]->fraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set ->correctanswers[]
|
||||
foreach ($answers as $answer) {
|
||||
|
||||
if ($answer->single) {
|
||||
$result->correctanswers =
|
||||
quiz_extract_correctanswers($answers, $nameprefix);
|
||||
break;
|
||||
|
||||
} else {
|
||||
if ($answer->fraction > 0.0) {
|
||||
$result->correctanswers[$nameprefix.$answer->id] = $answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[MULTICHOICE]= new quiz_multichoice_qtype();
|
||||
|
||||
?>
|
25
mod/quiz/questiontypes/numerical/editquestion.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
// This will only support one answer of the type NUMERICAL
|
||||
// However, lib.php has support for multiple answers
|
||||
if (!empty($question->id)) {
|
||||
$answersraw= quiz_get_answers($question);
|
||||
}
|
||||
$answers= array();
|
||||
for ($i=0; $i<6; $i++) {
|
||||
$answers[$i]->answer = ""; // Make answer slots, default as blank...
|
||||
$answers[$i]->min = "";
|
||||
$answers[$i]->max = "";
|
||||
$answers[$i]->feedback = "";
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingnumerical", "quiz"), "numerical", "quiz");
|
||||
require("numerical.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/numerical/icon.gif
Normal file
After Width: | Height: | Size: 84 B |
141
mod/quiz/questiontypes/numerical/numerical.html
Normal file
@ -0,0 +1,141 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("correctanswer", "quiz") ?>:</B></P></TD>
|
||||
<?php
|
||||
// Even thou the rest of the module can handle up to six numerical answers,
|
||||
// this form will limit the number of numerical answers to one only.
|
||||
if (is_numeric($answers[0]->min) && is_numeric($answers[0]->answer)) {
|
||||
$acceptederror = (float)($answers[0]->answer)
|
||||
- (float)($answers[0]->min);
|
||||
} else {
|
||||
$acceptederror = "";
|
||||
}
|
||||
?>
|
||||
<TD>
|
||||
<INPUT align="LEFT" type="text" id="correct0" name="answer[]" size="20" value="<?php p($answers[0]->answer) ?>"/>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("acceptederror", "quiz"); ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT align="LEFT" type="text" id="acceptederror0" name="acceptederror[]" size="15" value="<?php p($acceptederror) ?>" />±
|
||||
<!-- Values max and min will be determined when the form is submitted -->
|
||||
<INPUT type="HIDDEN" id="min0" name="min[]" value=""/>
|
||||
<INPUT type="HIDDEN" id="max0" name="max[]" value=""/>
|
||||
<INPUT type="HIDDEN" name="fraction[]" value="1"/>
|
||||
<BR/>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[0]->feedback) ?></textarea>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" onClick="return determineMinAndMax();" value="<?php print_string("savechanges") ?>">
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<SCRIPT language="JAVASCRIPT">
|
||||
function determineMinAndMax() {
|
||||
// This client-side script will determine the values for min and max
|
||||
// based on the input for answer and acceptederror.
|
||||
with(document.theform) {
|
||||
if (correct0.value=='') {
|
||||
alert('<?php print_string("missingcorrectanswer","quiz") ?>');
|
||||
return false;
|
||||
} else if (acceptederror0.value=='') {
|
||||
var correct= parseFloat(correct0.value);
|
||||
if (!isNaN(correct)) {
|
||||
min0.value= correct;
|
||||
max0.value= correct;
|
||||
}
|
||||
return true;
|
||||
} else if (isNaN(acceptederror0.value) || isNaN(correct0.value)) {
|
||||
alert('<?php print_string("answerswithacceptederrormarginmustbenumeric", "quiz") ?>');
|
||||
return false;
|
||||
} else {
|
||||
var correct= parseFloat(correct0.value);
|
||||
var error= Math.abs(acceptederror0.value);
|
||||
min0.value= correct-error;
|
||||
max0.value= correct+error;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</SCRIPT>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
||||
|
178
mod/quiz/questiontypes/numerical/questiontype.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
/////////////////
|
||||
/// NUMERICAL ///
|
||||
/////////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
|
||||
///
|
||||
/// This class contains some special features in order to make the
|
||||
/// question type embeddable within a multianswer (cloze) question
|
||||
///
|
||||
|
||||
/// This question type behaves like shortanswer in most cases.
|
||||
/// Therefore, it extends the shortanswer question type...
|
||||
|
||||
require_once("$CFG->dirroot/mod/quiz/questiontypes/shortanswer/questiontype.php");
|
||||
|
||||
class quiz_numerical_qtype extends quiz_shortanswer_qtype {
|
||||
|
||||
function get_answers($question, $addedcondition='') {
|
||||
// The added condition is one addition that has been added
|
||||
// to the behaviour of this question type in order to make
|
||||
// it embeddable within a multianswer (embedded cloze) question
|
||||
|
||||
global $CFG;
|
||||
|
||||
// There can be multiple answers
|
||||
return get_records_sql("SELECT a.*, n.min, n.max
|
||||
FROM {$CFG->prefix}quiz_numerical n,
|
||||
{$CFG->prefix}quiz_answers a
|
||||
WHERE a.question = '$question->id'
|
||||
AND n.answer = a.id "
|
||||
. $addedcondition);
|
||||
}
|
||||
|
||||
function name() {
|
||||
return 'numerical';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
|
||||
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
|
||||
$oldanswers = array();
|
||||
}
|
||||
|
||||
$answers = array();
|
||||
$maxfraction = -1;
|
||||
|
||||
// Insert all the new answers
|
||||
foreach ($question->answer as $key => $dataanswer) {
|
||||
if ($dataanswer != "") {
|
||||
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
|
||||
$answer = $oldanswer;
|
||||
$answer->answer = $dataanswer;
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!update_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not update quiz answer! (id=$answer->id)";
|
||||
return $result;
|
||||
}
|
||||
} else { // This is a completely new answer
|
||||
unset($answer);
|
||||
$answer->answer = $dataanswer;
|
||||
$answer->question = $question->id;
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!$answer->id = insert_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not insert quiz answer!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
$answers[] = $answer->id;
|
||||
if ($question->fraction[$key] > $maxfraction) {
|
||||
$maxfraction = $question->fraction[$key];
|
||||
}
|
||||
|
||||
if ($options = get_record("quiz_numerical", "answer", $answer->id)) {
|
||||
$options->min= $question->min[$key];
|
||||
$options->max= $question->max[$key];
|
||||
if (!update_record("quiz_numerical", $options)) {
|
||||
$result->error = "Could not update quiz numerical options! (id=$options->id)";
|
||||
return $result;
|
||||
}
|
||||
} else { // completely new answer
|
||||
unset($options);
|
||||
$options->question = $question->id;
|
||||
$options->answer = $answer->id;
|
||||
$options->min = $question->min[$key];
|
||||
$options->max = $question->max[$key];
|
||||
if (!insert_record("quiz_numerical", $options)) {
|
||||
$result->error = "Could not insert quiz numerical options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform sanity checks on fractional grades
|
||||
if ($maxfraction != 1) {
|
||||
$maxfraction = $maxfraction * 100;
|
||||
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
|
||||
return $result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix, $addedanswercondition='') {
|
||||
|
||||
$result->answers = array();
|
||||
if (isset($question->response[$nameprefix])) {
|
||||
$response = trim(stripslashes($question->response[$nameprefix]));
|
||||
if (!is_numeric($response) and is_numeric(
|
||||
$tmp = str_replace(',', '.', $response))) {
|
||||
/// I haven't ever needed to make a workaround like this
|
||||
/// before, I have no idea why I need to do it now...
|
||||
$response = $tmp;
|
||||
}
|
||||
} else {
|
||||
$response = NULL;
|
||||
}
|
||||
$answers = $this->get_answers($question, $addedanswercondition);
|
||||
foreach ($answers as $answer) {
|
||||
|
||||
/// Check if response matches answer...
|
||||
if ('' != $response and empty($result->answers)
|
||||
|| $answer->fraction
|
||||
> $result->answers[$nameprefix]->fraction
|
||||
and strtolower($response) == strtolower($answer->answer)
|
||||
|| '' != trim($answer->min)
|
||||
&& ((float)$response >= (float)$answer->min)
|
||||
&& ((float)$response <= (float)$answer->max)) {
|
||||
$result->answers[$nameprefix] = $answer;
|
||||
}
|
||||
}
|
||||
|
||||
$result->grade = isset($result->answers[$nameprefix])
|
||||
? $result->answers[$nameprefix]->fraction
|
||||
: 0.0;
|
||||
$result->correctanswers = quiz_extract_correctanswers($answers,
|
||||
$nameprefix);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// For numerical answer we have the policy to
|
||||
// set feedback for any response, even it the
|
||||
// response does not entitles the student to it.
|
||||
/////////////////////////////////////////////////
|
||||
if ('' !== $response and empty($result->answers)
|
||||
|| empty($result->answers[$nameprefix]->feedback)) {
|
||||
// Look for just any feedback:
|
||||
foreach ($result->correctanswers as $correctanswer) {
|
||||
if ($correctanswer->feedback) {
|
||||
$result->answers[$nameprefix]->feedback =
|
||||
$correctanswer->feedback;
|
||||
if (empty($result->answers[$nameprefix]->id)) {
|
||||
// Better fake an answer as well:
|
||||
$result->answers[$nameprefix]->id = 0;
|
||||
$result->answers[$nameprefix]->answer = $response;
|
||||
$result->answers[$nameprefix]->fraction = 0.0;
|
||||
$result->answers[$nameprefix]->question = $question->id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[NUMERICAL]= new quiz_numerical_qtype();
|
||||
|
||||
?>
|
6
mod/quiz/questiontypes/random/editquestion.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
print_heading_with_help(get_string("editingrandom", "quiz"), "random", "quiz");
|
||||
require("random.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/random/icon.gif
Normal file
After Width: | Height: | Size: 143 B |
206
mod/quiz/questiontypes/random/questiontype.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
//////////////
|
||||
/// RANDOM ///
|
||||
//////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
class quiz_random_qtype extends quiz_default_questiontype {
|
||||
|
||||
var $possiblerandomqtypes = array(SHORTANSWER,
|
||||
NUMERICAL,
|
||||
MULTICHOICE,
|
||||
MATCH,
|
||||
// RANDOMSAMATCH,// Can cause unexpected outcomes
|
||||
TRUEFALSE,
|
||||
MULTIANSWER);
|
||||
|
||||
// Carries questions available as randoms sorted by category
|
||||
// This array is used when needed only
|
||||
var $catrandoms = array();
|
||||
|
||||
function name() {
|
||||
return 'random';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
/// No options to be saved for this question type:
|
||||
return true;
|
||||
}
|
||||
|
||||
function wrapped_questions($question) {
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
foreach ($question->response as $key => $response) {
|
||||
if (ereg('[^0-9][0-9]+random$', $key)) {
|
||||
$randomquestion = get_record('quiz_questions',
|
||||
'id', $response);
|
||||
$randomquestion->response = $question->response;
|
||||
unset($randomquestion->response[$key]);
|
||||
if ($subwrapped = $QUIZ_QTYPES[$randomquestion->qtype]
|
||||
->wrapped_questions($randomquestion)) {
|
||||
return "$response,$subwrapped";
|
||||
} else {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function convert_to_response_answer_field($questionresponse) {
|
||||
/// THIS IS PART OF A WORKAROUND AS THIS IS THE ONLY
|
||||
/// CASE WHERE IT IS NEEDED TO STORE TWO RESPONSE RECORDS...
|
||||
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
foreach ($questionresponse as $key => $response) {
|
||||
if (ereg('[^0-9][0-9]+random$', $key)) {
|
||||
unset($questionresponse[$key]);
|
||||
$randomquestion = get_record('quiz_questions',
|
||||
'id', $response);
|
||||
return "random$response-"
|
||||
.$QUIZ_QTYPES[$randomquestion->qtype]
|
||||
->convert_to_response_answer_field($questionresponse);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function create_response($question, $nameprefix, $questionsinuse) {
|
||||
// It's for question types like RANDOMSAMATCH and RANDOM that
|
||||
// the true power of the pattern with this function comes to the surface.
|
||||
// This implementation will stand even after a possible exclusion of
|
||||
// the funtions extract_response and convert_to_response_answer_field
|
||||
global $CFG;
|
||||
|
||||
if (!isset($this->catrandoms[$question->category])) {
|
||||
//Need to fetch random questions from category $question->category"
|
||||
|
||||
$possiblerandomqtypes = "'"
|
||||
. implode("','", $this->possiblerandomqtypes) . "'";
|
||||
$this->catrandoms[$question->category] = get_records_sql
|
||||
("SELECT * FROM {$CFG->prefix}quiz_questions
|
||||
WHERE category = '$question->category'
|
||||
AND id NOT IN ($questionsinuse)
|
||||
AND qtype IN ($possiblerandomqtypes)");
|
||||
shuffle($this->catrandoms[$question->category]);
|
||||
}
|
||||
|
||||
while ($randomquestion =
|
||||
array_pop($this->catrandoms[$question->category])) {
|
||||
if (!ereg("(^|,)$randomquestion->id(,|$)", $questionsinuse)) {
|
||||
/// $randomquestion is not in use and will therefore be used
|
||||
/// as the randomquestion here...
|
||||
|
||||
global $QUIZ_QTYPES;
|
||||
$response = $QUIZ_QTYPES[$randomquestion->qtype]
|
||||
->create_response($randomquestion,
|
||||
quiz_qtype_nameprefix($randomquestion, $nameprefix),
|
||||
"$questionsinuse,$randomquestion->id");
|
||||
$response[$nameprefix] = $randomquestion->id;
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
notify(get_string('toomanyrandom', 'quiz', $question->category));
|
||||
return array();
|
||||
}
|
||||
|
||||
function extract_response($rawresponse, $nameprefix) {
|
||||
global $QUIZ_QTYPES;
|
||||
if ($randomquestion = get_record('quiz_questions',
|
||||
'id', $rawresponse->answer)) {
|
||||
if ($randomresponse = get_record
|
||||
('quiz_responses', 'question', $rawresponse->answer,
|
||||
'attempt', $rawresponse->attempt)) {
|
||||
|
||||
/// The prefered case:
|
||||
// The response field for the random question was found
|
||||
// the response array can be extracted:
|
||||
|
||||
$response = $QUIZ_QTYPES[$randomquestion->qtype]
|
||||
->extract_response($randomresponse,
|
||||
quiz_qtype_nameprefix($randomquestion, $nameprefix));
|
||||
|
||||
} else {
|
||||
notify("Error: Cannot find response to random question $randomquestion->id");
|
||||
|
||||
/// Instead: workaround by creating a new response:
|
||||
$response = $QUIZ_QTYPES[$randomquestion->qtype]
|
||||
->create_response($randomquestion,
|
||||
quiz_qtype_nameprefix($randomquestion, $nameprefix),
|
||||
"$rawresponse->question,$rawresponse->answer");
|
||||
// (That last argument is instead of $questionsinuse.
|
||||
// It is not correct but it would be very messy to
|
||||
// determine the correct value, while very few
|
||||
// question types actually use it and they who do have
|
||||
// good chances to execute properly anyway.)
|
||||
}
|
||||
$response[$nameprefix] = $randomquestion->id;
|
||||
return $response;
|
||||
} else {
|
||||
notify("Error: Unable to find random question $rawresponse->question");
|
||||
/// No new random question is picked as this is probably
|
||||
/// not what the moodle user has in mind anyway
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
// Get the wrapped question...
|
||||
if ($actualquestion = $this->get_wrapped_question($question,
|
||||
$nameprefix)) {
|
||||
echo '<input type="hidden" name="' . $nameprefix
|
||||
. '" value="' . $actualquestion->id . '"/>';
|
||||
return $QUIZ_QTYPES[$actualquestion->qtype]
|
||||
->print_question_formulation_and_controls($actualquestion,
|
||||
$quiz, $readonly, $answers, $correctanswers,
|
||||
quiz_qtype_nameprefix($actualquestion, $nameprefix));
|
||||
} else {
|
||||
echo '<p>' . get_string('random', 'quiz') . '</p>';
|
||||
}
|
||||
}
|
||||
|
||||
function get_wrapped_question($question, $nameprefix) {
|
||||
if (!empty($question->response[$nameprefix])
|
||||
and $actualquestion = get_record('quiz_questions',
|
||||
'id', $question->response[$nameprefix],
|
||||
// The category check is a security check
|
||||
'category', $question->category)) {
|
||||
$actualquestion->response = $question->response;
|
||||
unset($actualquestion->response[$nameprefix]);
|
||||
$actualquestion->maxgrade = $question->maxgrade;
|
||||
return $actualquestion;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
// Get the wrapped question...
|
||||
if ($actualquestion = $this->get_wrapped_question($question,
|
||||
$nameprefix)) {
|
||||
return $QUIZ_QTYPES[$actualquestion->qtype]->grade_response(
|
||||
$actualquestion,
|
||||
quiz_qtype_nameprefix($actualquestion, $nameprefix));
|
||||
} else {
|
||||
$result->grade = 0.0;
|
||||
$result->answers = array();
|
||||
$result->correctanswers = array();
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[RANDOM]= new quiz_random_qtype();
|
||||
|
||||
?>
|
30
mod/quiz/questiontypes/random/random.html
Normal file
@ -0,0 +1,30 @@
|
||||
<center>
|
||||
<form name="theform" method="post" action="question.php">
|
||||
|
||||
<table cellpadding=5>
|
||||
<tr valign=top>
|
||||
<td align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<td>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign=top>
|
||||
<td align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<td>
|
||||
<?php if (empty($question->name)) {
|
||||
$question->name = get_string("random", "quiz");
|
||||
} ?>
|
||||
<input type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="hidden" name=questiontext value="---">
|
||||
|
||||
<input type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<input type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<input type="submit" value="<?php print_string("savechanges") ?>">
|
||||
</form>
|
||||
</center>
|
||||
|
11
mod/quiz/questiontypes/randomsamatch/editquestion.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?PHP // $Id$
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_randomsamatch", "question", $question->id);
|
||||
} else {
|
||||
$options->choose = "";
|
||||
}
|
||||
$numberavailable = count_records("quiz_questions", "category", $category->id, "qtype", SHORTANSWER);
|
||||
print_heading_with_help(get_string("editingrandomsamatch", "quiz"), "randomsamatch", "quiz");
|
||||
require("randomsamatch.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/randomsamatch/icon.gif
Normal file
After Width: | Height: | Size: 204 B |
231
mod/quiz/questiontypes/randomsamatch/questiontype.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
/////////////////////
|
||||
/// RANDOMSAMATCH ///
|
||||
/////////////////////
|
||||
|
||||
/// The use of this question type together with the
|
||||
/// question type RANDOM within the same quiz can cause
|
||||
/// a shortanswer question to appear in a RANDOM question
|
||||
/// as well as one of the matcher questions in a question of this type
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
class quiz_randomsamatch_qtype extends quiz_match_qtype {
|
||||
/// Extends MATCH as there are quite a few simularities...
|
||||
|
||||
// $catrandoms carries question ids for shortanswer questions
|
||||
// available as random questios.
|
||||
// They are sorted by category.
|
||||
var $catrandoms = array();
|
||||
|
||||
function name() {
|
||||
return 'randomsamatch';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
$options->question = $question->id;
|
||||
$options->choose = $question->choose;
|
||||
if ($existing = get_record("quiz_randomsamatch",
|
||||
"question", $options->question)) {
|
||||
$options->id = $existing->id;
|
||||
if (!update_record("quiz_randomsamatch", $options)) {
|
||||
$result->error = "Could not update quiz randomsamatch options!";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
if (!insert_record("quiz_randomsamatch", $options)) {
|
||||
$result->error = "Could not insert quiz randomsamatch options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function wrapped_questions($question) {
|
||||
if (empty($question->response)) {
|
||||
return false;
|
||||
} else {
|
||||
$wrapped = '';
|
||||
$delimiter = '';
|
||||
foreach ($question->response as $rkey => $response) {
|
||||
$wrapped .= $delimiter.$this->extract_response_id($rkey);
|
||||
$delimiter = ',';
|
||||
}
|
||||
return $wrapped;
|
||||
}
|
||||
}
|
||||
|
||||
function create_response($question, $nameprefix, $questionsinuse) {
|
||||
// It's for question types like RANDOMSAMATCH and RANDOM that
|
||||
// the true power of the pattern with this function comes to the surface.
|
||||
// This implementation will stand even after a possible exclusion of
|
||||
// the funtions extract_response and convert_to_response_answer_field
|
||||
|
||||
if (!isset($this->catrandoms[$question->category])) {
|
||||
/// Need to fetch the shortanswer question ids for the category:
|
||||
|
||||
$saquestions = get_records_select('quiz_questions',
|
||||
" category='$question->category'
|
||||
AND qtype='".SHORTANSWER."'
|
||||
AND id NOT IN ($questionsinuse) ");
|
||||
$this->catrandoms[$question->category] = array_keys($saquestions);
|
||||
shuffle($this->catrandoms[$question->category]);
|
||||
}
|
||||
|
||||
/// Access question options to find out how many short-answer
|
||||
/// questions we are supposed to pick...
|
||||
if ($options = get_record('quiz_randomsamatch',
|
||||
'question', $question->id)) {
|
||||
$questionstopick = $options->choose;
|
||||
} else {
|
||||
notify("Error: Missing question options! - Try to pick two shortanswer questions anyway");
|
||||
$questionstopick = 2;
|
||||
}
|
||||
|
||||
/// Pick the short-answer question ids and create the $response array
|
||||
$response = array();
|
||||
while ($questionstopick) {
|
||||
$said = array_pop($this->catrandoms[$question->category]);
|
||||
if (!ereg("(^|,)$said(,|$)", $questionsinuse)) {
|
||||
$response[$nameprefix.$said] = '0';
|
||||
--$questionstopick;
|
||||
}
|
||||
}
|
||||
|
||||
if ($questionstopick) {
|
||||
notify("Error: could not get enough Short-Answer questions!");
|
||||
$count = count($response);
|
||||
$wanted = $count + $questionstopick;
|
||||
notify("Got $count Short-Answer questions, but wanted $wanted.");
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
function extract_response($rawresponse, $nameprefix) {
|
||||
/// Simple implementation that does not check with the database
|
||||
/// and thus - does not bother to check whether there has been
|
||||
/// any changes to the question options.
|
||||
$response = array();
|
||||
$rawitems = explode(',', $rawresponse->answer);
|
||||
foreach ($rawitems as $rawitem) {
|
||||
$splits = explode('-', $rawitem, 2);
|
||||
$response[$nameprefix.$splits[0]] = $splits[1];
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
|
||||
// Print question formulation
|
||||
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat, NULL, $quiz->course);
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
// Summarize shortanswer questions answer alternatives:
|
||||
if (empty($correctanswers)) {
|
||||
// Get them using the grade_response method
|
||||
$tempresult = $this->grade_response($question, $nameprefix);
|
||||
$saanswers = $tempresult->correctanswers;
|
||||
} else {
|
||||
$saanswers = $correctanswers;
|
||||
}
|
||||
foreach ($saanswers as $key => $saanswer) {
|
||||
unset($saanswers[$key]); // Unsets the nameprefix occurence
|
||||
$saanswers[$saanswer->id] = trim($saanswer->answer);
|
||||
}
|
||||
$saanswers = draw_rand_array($saanswers, count($saanswers));
|
||||
|
||||
// Print the shortanswer questions and input controls:
|
||||
echo '<table border="0" cellpadding="10">';
|
||||
foreach ($question->response as $inputname => $response) {
|
||||
if (!($saquestion = get_record('quiz_questions', 'id',
|
||||
quiz_extract_posted_id($inputname, $nameprefix)))) {
|
||||
notify("Error: cannot find shortanswer question for $inputname ");
|
||||
continue;
|
||||
}
|
||||
|
||||
echo '<tr><td align="left" valign="top">';
|
||||
echo $saquestion->questiontext;
|
||||
echo '</td>';
|
||||
echo '<td align="right" valign="top">';
|
||||
if (!empty($correctanswers)
|
||||
&& $correctanswers[$inputname]->id == $response) {
|
||||
echo '<span="highlight">';
|
||||
choose_from_menu($saanswers, $inputname, $response);
|
||||
echo '</span><br />';
|
||||
} else {
|
||||
choose_from_menu($saanswers, $inputname, $response);
|
||||
if ($readonly && $quiz->correctanswers
|
||||
&& isset($correctanswer[$inputname])) {
|
||||
quiz_print_correctanswer($correctanswer[$inputname]->answer);
|
||||
}
|
||||
}
|
||||
if ($quiz->feedback && isset($answers[$inputname])
|
||||
&& $answers[$inputname]->feedback) {
|
||||
quiz_print_comment($answers[$inputname]->feedback);
|
||||
}
|
||||
echo '</td></tr>';
|
||||
}
|
||||
echo '</table>';
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
global $QUIZ_QTYPES;
|
||||
|
||||
$result->answers = array();
|
||||
$result->correctanswers = array();
|
||||
$result->grade = 0.0;
|
||||
|
||||
foreach ($question->response as $inputname => $subresponse) {
|
||||
if ($subquestion = get_record('quiz_questions',
|
||||
'id', quiz_extract_posted_id($inputname, $nameprefix),
|
||||
// These two query conditions are security checks that prevents cheating...
|
||||
'qtype', SHORTANSWER,
|
||||
'category', $question->category)) {
|
||||
|
||||
if ($subresponse = get_record('quiz_answers',
|
||||
'id', $subresponse)) {
|
||||
$subquestion->response[$inputname] = $subresponse->answer;
|
||||
} else {
|
||||
$subquestion->response[$inputname] = '';
|
||||
}
|
||||
|
||||
// Use the shortanswer framework to for grading...
|
||||
$subresult = $QUIZ_QTYPES[SHORTANSWER]
|
||||
->grade_response($subquestion, $inputname);
|
||||
|
||||
// Summarize shortanswer results
|
||||
if (isset($subresult->answers[$inputname])) {
|
||||
$result->answers[$inputname] =
|
||||
$subresult->answers[$inputname];
|
||||
$result->grade += $result->answers[$inputname]->fraction;
|
||||
if ($result->answers[$inputname]->fraction >= 1.0) {
|
||||
$result->correctanswers[$inputname] =
|
||||
$result->answers[$inputname];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Pick the first correctanswer:
|
||||
foreach ($subresult->correctanswers as $correct) {
|
||||
$result->correctanswers[$inputname] = $correct;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($result->grade) {
|
||||
$result->grade /= count($question->response);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[RANDOMSAMATCH]= new quiz_randomsamatch_qtype();
|
||||
|
||||
?>
|
98
mod/quiz/questiontypes/randomsamatch/randomsamatch.html
Normal file
@ -0,0 +1,98 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php 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><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?PHP
|
||||
if (empty($question->name)) {
|
||||
$question->name = get_string("randomsamatch", "quiz");
|
||||
}
|
||||
?>
|
||||
<INPUT type="text" name="name" size=40 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align=right><p><b><?php print_string("introduction", "quiz") ?>:</b></p></td>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
if (empty($question->questiontext)) {
|
||||
$question->questiontext = get_string("randomsamatchintro", "quiz");
|
||||
}
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!$question->questiontextformat) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("randomsamatchnumber", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php
|
||||
if ($numberavailable < 2) {
|
||||
echo "ERROR";
|
||||
$maxrandom=2;
|
||||
} else if ($numberavailable < 6) {
|
||||
$maxrandom = $numberavailable;
|
||||
} else {
|
||||
$maxrandom = QUIZ_MAX_NUMBER_ANSWERS;
|
||||
}
|
||||
|
||||
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="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
23
mod/quiz/questiontypes/shortanswer/editquestion.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?PHP // $Id$
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_shortanswer", "question", $question->id);
|
||||
} else {
|
||||
$options->usecase = 0;
|
||||
}
|
||||
if (!empty($options->answers)) {
|
||||
$answersraw = get_records_list("quiz_answers", "id", $options->answers);
|
||||
}
|
||||
for ($i=0; $i<QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
$answers[] = ""; // Make answer slots, default as blank
|
||||
}
|
||||
if (!empty($answersraw)) {
|
||||
$i=0;
|
||||
foreach ($answersraw as $answer) {
|
||||
$answers[$i] = $answer; // insert answers into slots
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
print_heading_with_help(get_string("editingshortanswer", "quiz"), "shortanswer", "quiz");
|
||||
require("shortanswer.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/shortanswer/icon.gif
Normal file
After Width: | Height: | Size: 74 B |
198
mod/quiz/questiontypes/shortanswer/questiontype.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
///////////////////
|
||||
/// SHORTANSWER ///
|
||||
///////////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
|
||||
///
|
||||
/// This class contains some special features in order to make the
|
||||
/// question type embeddable within a multianswer (cloze) question
|
||||
///
|
||||
|
||||
class quiz_shortanswer_qtype extends quiz_default_questiontype {
|
||||
|
||||
function get_answers($question, $addedcondition='') {
|
||||
// The added condition is one addition that has been added
|
||||
// to the behaviour of this question type in order to make
|
||||
// it embeddable within a multianswer (embedded cloze) question
|
||||
|
||||
global $CFG;
|
||||
|
||||
// There can 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 "
|
||||
. $addedcondition);
|
||||
|
||||
}
|
||||
|
||||
function name() {
|
||||
return 'shortanswer';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
|
||||
$oldanswers = array();
|
||||
}
|
||||
|
||||
$answers = array();
|
||||
$maxfraction = -1;
|
||||
|
||||
// Insert all the new answers
|
||||
foreach ($question->answer as $key => $dataanswer) {
|
||||
if ($dataanswer != "") {
|
||||
if ($oldanswer = array_shift($oldanswers)) { // Existing answer, so reuse it
|
||||
$answer = $oldanswer;
|
||||
$answer->answer = trim($dataanswer);
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!update_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not update quiz answer! (id=$answer->id)";
|
||||
return $result;
|
||||
}
|
||||
} else { // This is a completely new answer
|
||||
unset($answer);
|
||||
$answer->answer = trim($dataanswer);
|
||||
$answer->question = $question->id;
|
||||
$answer->fraction = $question->fraction[$key];
|
||||
$answer->feedback = $question->feedback[$key];
|
||||
if (!$answer->id = insert_record("quiz_answers", $answer)) {
|
||||
$result->error = "Could not insert quiz answer!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
$answers[] = $answer->id;
|
||||
if ($question->fraction[$key] > $maxfraction) {
|
||||
$maxfraction = $question->fraction[$key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($options = get_record("quiz_shortanswer", "question", $question->id)) {
|
||||
$options->answers = implode(",",$answers);
|
||||
$options->usecase = $question->usecase;
|
||||
if (!update_record("quiz_shortanswer", $options)) {
|
||||
$result->error = "Could not update quiz shortanswer options! (id=$options->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($options);
|
||||
$options->question = $question->id;
|
||||
$options->answers = implode(",",$answers);
|
||||
$options->usecase = $question->usecase;
|
||||
if (!insert_record("quiz_shortanswer", $options)) {
|
||||
$result->error = "Could not insert quiz shortanswer options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform sanity checks on fractional grades
|
||||
if ($maxfraction != 1) {
|
||||
$maxfraction = $maxfraction * 100;
|
||||
$result->noticeyesno = get_string("fractionsnomax", "quiz", $maxfraction);
|
||||
return $result;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
/// This implementation is also used by question type NUMERICAL
|
||||
|
||||
/// Print question text and media
|
||||
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
/// Print input controls
|
||||
|
||||
$stranswer = get_string("answer", "quiz");
|
||||
if (isset($question->response[$nameprefix])) {
|
||||
$value = ' value="'.$question->response[$nameprefix].'" ';
|
||||
} else {
|
||||
$value = ' value="" ';
|
||||
}
|
||||
$inputname = ' name="'.$nameprefix.'" ';
|
||||
echo "<p align=\"right\">$stranswer: <input type=\"text\" $readonly $inputname size=\"80\" $value /></p>";
|
||||
|
||||
if ($quiz->feedback && isset($answers[$nameprefix])
|
||||
&& $feedback = $answers[$nameprefix]->feedback) {
|
||||
quiz_print_comment("<p align=\"right\">$feedback</p>");
|
||||
}
|
||||
if ($readonly && $quiz->correctanswers) {
|
||||
$delimiter = '';
|
||||
$correct = '';
|
||||
foreach ($correctanswers as $correctanswer) {
|
||||
$correct .= $delimiter.$correctanswer->answer;
|
||||
$delimiter = ', ';
|
||||
}
|
||||
quiz_print_correctanswer($correct);
|
||||
}
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix, $addedanswercondition='') {
|
||||
|
||||
if (isset($question->response[$nameprefix])) {
|
||||
$response0 = trim(stripslashes($question->response[$nameprefix]));
|
||||
} else {
|
||||
$response0 = '';
|
||||
}
|
||||
$answers = $this->get_answers($question, $addedanswercondition);
|
||||
|
||||
/// Determine ->answers[]
|
||||
$result->answers = array();
|
||||
if ('' !== $response0) {
|
||||
foreach ($answers as $answer) {
|
||||
|
||||
$answer->answer = trim($answer->answer); // Just in case
|
||||
|
||||
if (empty($result->answers) || $answer->fraction
|
||||
> $result->answers[$nameprefix]->fraction) {
|
||||
|
||||
if (!$answer->usecase) { // Don't compare case
|
||||
$response0 = strtolower($response0);
|
||||
$answer0 = strtolower($answer->answer);
|
||||
} else {
|
||||
$answer0 = $answer->answer;
|
||||
}
|
||||
|
||||
if (strpos(' '.$answer0, '*')) {
|
||||
$answer0 = str_replace('\*','@@@@@@',$answer0);
|
||||
$answer0 = str_replace('*','.*',$answer0);
|
||||
$answer0 = str_replace('@@@@@@', '\*',$answer0);
|
||||
$answer0 = str_replace('+', '\+',$answer0);
|
||||
if (ereg('^'.$answer0.'$', $response0)) {
|
||||
$result->answers[$nameprefix] = $answer;
|
||||
}
|
||||
|
||||
} else if ($answer0 == $response0) {
|
||||
$result->answers[$nameprefix] = $answer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$result->grade = isset($result->answers[$nameprefix])
|
||||
? $result->answers[$nameprefix]->fraction
|
||||
: 0.0;
|
||||
$result->correctanswers = quiz_extract_correctanswers($answers,
|
||||
$nameprefix);
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[SHORTANSWER]= new quiz_shortanswer_qtype();
|
||||
|
||||
?>
|
126
mod/quiz/questiontypes/shortanswer/shortanswer.html
Normal file
@ -0,0 +1,126 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php quiz_category_select_menu($course->id, true, true); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("casesensitive", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php
|
||||
unset($menu);
|
||||
$menu[0] = get_string("caseno", "quiz");
|
||||
$menu[1] = get_string("caseyes", "quiz");
|
||||
choose_from_menu($menu, "usecase", "$options->usecase", "");
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("correctanswers", "quiz") ?></B>:</P></TD>
|
||||
<TD>
|
||||
<P><?php print_string("filloutoneanswer", "quiz") ?></P>
|
||||
</TD>
|
||||
|
||||
|
||||
<?PHP
|
||||
for ($i=1; $i<=QUIZ_MAX_NUMBER_ANSWERS; $i++) {
|
||||
?>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php echo get_string("answer", "quiz")." $i"; ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="answer[]" size=50 value="<?php p($answers[$i-1]->answer) ?>">
|
||||
<?php print_string("grade");
|
||||
echo ": ";
|
||||
choose_from_menu($gradeoptions, "fraction[]", $answers[$i-1]->fraction,""); ?>
|
||||
<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("feedback", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="feedback[]" rows=2 cols=50 wrap="virtual"><?php p($answers[$i-1]->feedback) ?></textarea>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD colspan=2> </TD>
|
||||
</TR>
|
||||
|
||||
<?PHP
|
||||
}
|
||||
?>
|
||||
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
28
mod/quiz/questiontypes/truefalse/editquestion.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
if (!empty($question->id)) {
|
||||
$options = get_record("quiz_truefalse", "question", "$question->id");
|
||||
}
|
||||
if (!empty($options->trueanswer)) {
|
||||
$true = get_record("quiz_answers", "id", $options->trueanswer);
|
||||
} else {
|
||||
$true->fraction = 1;
|
||||
$true->feedback = "";
|
||||
}
|
||||
if (!empty($options->falseanswer)) {
|
||||
$false = get_record("quiz_answers", "id", "$options->falseanswer");
|
||||
} else {
|
||||
$false->fraction = 0;
|
||||
$false->feedback = "";
|
||||
}
|
||||
|
||||
if ($true->fraction > $false->fraction) {
|
||||
$question->answer = 1;
|
||||
} else {
|
||||
$question->answer = 0;
|
||||
}
|
||||
|
||||
print_heading_with_help(get_string("editingtruefalse", "quiz"), "truefalse", "quiz");
|
||||
require("truefalse.html");
|
||||
|
||||
?>
|
BIN
mod/quiz/questiontypes/truefalse/icon.gif
Normal file
After Width: | Height: | Size: 190 B |
179
mod/quiz/questiontypes/truefalse/questiontype.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?PHP // $Id$
|
||||
|
||||
/////////////////
|
||||
/// TRUEFALSE ///
|
||||
/////////////////
|
||||
|
||||
/// QUESTION TYPE CLASS //////////////////
|
||||
class quiz_truefalse_qtype extends quiz_default_questiontype {
|
||||
|
||||
function name() {
|
||||
return 'truefalse';
|
||||
}
|
||||
|
||||
function save_question_options($question) {
|
||||
if (!$oldanswers = get_records("quiz_answers", "question", $question->id, "id ASC")) {
|
||||
$oldanswers = array();
|
||||
}
|
||||
|
||||
if ($true = array_shift($oldanswers)) { // Existing answer, so reuse it
|
||||
$true->answer = get_string("true", "quiz");
|
||||
$true->fraction = $question->answer;
|
||||
$true->feedback = $question->feedbacktrue;
|
||||
if (!update_record("quiz_answers", $true)) {
|
||||
$result->error = "Could not update quiz answer \"true\")!";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($true);
|
||||
$true->answer = get_string("true", "quiz");
|
||||
$true->question = $question->id;
|
||||
$true->fraction = $question->answer;
|
||||
$true->feedback = $question->feedbacktrue;
|
||||
if (!$true->id = insert_record("quiz_answers", $true)) {
|
||||
$result->error = "Could not insert quiz answer \"true\")!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if ($false = array_shift($oldanswers)) { // Existing answer, so reuse it
|
||||
$false->answer = get_string("false", "quiz");
|
||||
$false->fraction = 1 - (int)$question->answer;
|
||||
$false->feedback = $question->feedbackfalse;
|
||||
if (!update_record("quiz_answers", $false)) {
|
||||
$result->error = "Could not insert quiz answer \"false\")!";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($false);
|
||||
$false->answer = get_string("false", "quiz");
|
||||
$false->question = $question->id;
|
||||
$false->fraction = 1 - (int)$question->answer;
|
||||
$false->feedback = $question->feedbackfalse;
|
||||
if (!$false->id = insert_record("quiz_answers", $false)) {
|
||||
$result->error = "Could not insert quiz answer \"false\")!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
if ($options = get_record("quiz_truefalse", "question", $question->id)) {
|
||||
// No need to do anything, since the answer IDs won't have changed
|
||||
// But we'll do it anyway, just for robustness
|
||||
$options->trueanswer = $true->id;
|
||||
$options->falseanswer = $false->id;
|
||||
if (!update_record("quiz_truefalse", $options)) {
|
||||
$result->error = "Could not update quiz truefalse options! (id=$options->id)";
|
||||
return $result;
|
||||
}
|
||||
} else {
|
||||
unset($options);
|
||||
$options->question = $question->id;
|
||||
$options->trueanswer = $true->id;
|
||||
$options->falseanswer = $false->id;
|
||||
if (!insert_record("quiz_truefalse", $options)) {
|
||||
$result->error = "Could not insert quiz truefalse options!";
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function print_question_formulation_and_controls($question,
|
||||
$quiz, $readonly, $answers, $correctanswers, $nameprefix) {
|
||||
|
||||
// Get additional information from database
|
||||
|
||||
if (!$options = get_record("quiz_truefalse", "question", $question->id)) {
|
||||
notify("Error: Missing question options!");
|
||||
}
|
||||
if (!$true = get_record("quiz_answers", "id", $options->trueanswer)) {
|
||||
notify("Error: Missing question answers!");
|
||||
}
|
||||
if (!$false = get_record("quiz_answers", "id", $options->falseanswer)) {
|
||||
notify("Error: Missing question answers!");
|
||||
}
|
||||
|
||||
// Print question formulation
|
||||
|
||||
echo format_text($question->questiontext,
|
||||
$question->questiontextformat,
|
||||
NULL, $quiz->course);
|
||||
quiz_print_possible_question_image($quiz->id, $question);
|
||||
|
||||
// Print input controls
|
||||
|
||||
$stranswer = get_string("answer", "quiz");
|
||||
|
||||
if (!$true->answer) {
|
||||
$true->answer = get_string("true", "quiz");
|
||||
}
|
||||
if (!$false->answer) {
|
||||
$false->answer = get_string("false", "quiz");
|
||||
}
|
||||
|
||||
$truechecked = "";
|
||||
$falsechecked = "";
|
||||
|
||||
if (!isset($question->response[$nameprefix])) {
|
||||
$question->response[$nameprefix] = '';
|
||||
}
|
||||
if ($true->id == $question->response[$nameprefix]) {
|
||||
$truechecked = 'checked="checked"';
|
||||
} else if ($false->id == $question->response[$nameprefix]) {
|
||||
$falsechecked = 'checked="checked"';
|
||||
}
|
||||
if ($readonly) {
|
||||
$readonly = ' readonly="readonly" disabled="disabled" ';
|
||||
}
|
||||
|
||||
$truecorrect = "";
|
||||
$falsecorrect = "";
|
||||
if ($readonly && $quiz->correctanswers) {
|
||||
if (!empty($correctanswers[$nameprefix.$true->id])) {
|
||||
$truecorrect = 'class="highlight"';
|
||||
}
|
||||
if (!empty($correctanswers[$nameprefix.$false->id])) {
|
||||
$falsecorrect = 'class="highlight"';
|
||||
}
|
||||
}
|
||||
$inputname = ' name="'.$nameprefix.'" ';
|
||||
echo "<table align=\"right\" cellpadding=\"5\"><tr><td align=\"right\">$stranswer: ";
|
||||
echo "<td $truecorrect>";
|
||||
echo "<input $truechecked type=\"radio\" $readonly $inputname value=\"$true->id\" />$true->answer";
|
||||
echo "</td><td $falsecorrect>";
|
||||
echo "<input $falsechecked type=\"radio\" $readonly $inputname value=\"$false->id\" />$false->answer";
|
||||
echo "</td></tr></table><br clear=\"all\">";// changed from CLEAR=ALL jm
|
||||
if ($quiz->feedback && isset($answers[$nameprefix])
|
||||
&& $feedback = $answers[$nameprefix]->feedback) {
|
||||
quiz_print_comment(
|
||||
"<p align=\"right\">$feedback</p>");
|
||||
}
|
||||
}
|
||||
|
||||
function grade_response($question, $nameprefix) {
|
||||
|
||||
$answers = get_records("quiz_answers", "question", $question->id);
|
||||
if (isset($question->response[$nameprefix])
|
||||
&& isset($answers[$question->response[$nameprefix]])) {
|
||||
$result->answers = array($nameprefix
|
||||
=> $answers[$question->response[$nameprefix]]);
|
||||
$result->grade = $result->answers[$nameprefix]->fraction;
|
||||
|
||||
} else {
|
||||
$result->answers = array();
|
||||
$result->grade = 0.0;
|
||||
}
|
||||
$result->correctanswers = quiz_extract_correctanswers($answers,
|
||||
$nameprefix);
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
//// END OF CLASS ////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
//// INITIATION - Without this line the question type is not in use... ///
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
$QUIZ_QTYPES[TRUEFALSE]= new quiz_truefalse_qtype();
|
||||
|
||||
?>
|
103
mod/quiz/questiontypes/truefalse/truefalse.html
Normal file
@ -0,0 +1,103 @@
|
||||
<FORM name="theform" method="post" <?php echo $onsubmit ?> action="question.php">
|
||||
<CENTER>
|
||||
<TABLE cellpadding=5>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("category", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php
|
||||
quiz_category_select_menu($course->id, true, true);
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("questionname", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<INPUT type="text" name="name" size=50 value="<?php p($question->name) ?>">
|
||||
<?php if (isset($err["name"])) formerr($err["name"]); ?>
|
||||
</TD>
|
||||
</TR>
|
||||
<tr valign=top>
|
||||
<td align="right"><p><b><?php print_string("question", "quiz") ?>:</b></p>
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<p><font SIZE="1">
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
helpbutton("richtext", get_string("helprichtext"), "moodle", true, true);
|
||||
} else {
|
||||
helpbutton("text", get_string("helptext"), "moodle", true, true);
|
||||
}
|
||||
?>
|
||||
</font></p>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (isset($err["questiontext"])) {
|
||||
formerr($err["questiontext"]);
|
||||
echo "<br />";
|
||||
}
|
||||
|
||||
print_textarea($usehtmleditor, 15, 60, 630, 300, "questiontext", $question->questiontext);
|
||||
|
||||
if ($usehtmleditor) { /// Trying this out for a while
|
||||
echo '<input type="hidden" name="questiontextformat" value="'.FORMAT_HTML.'">';
|
||||
} else {
|
||||
echo "<div align=right>";
|
||||
print_string("formattexttype");
|
||||
echo ": ";
|
||||
if (!isset($question->questiontextformat)) {
|
||||
$question->questiontextformat = FORMAT_MOODLE;
|
||||
}
|
||||
choose_from_menu(format_text_menu(), "questiontextformat", $question->questiontextformat, "");
|
||||
helpbutton("textformat", get_string("helpformatting"));
|
||||
echo "</div>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("imagedisplay", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php if (empty($images)) {
|
||||
print_string("noimagesyet");
|
||||
} else {
|
||||
choose_from_menu($images, "image", "$question->image", get_string("none"),"","");
|
||||
}
|
||||
?>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("correctanswer", "quiz") ?>:</B></P></TD>
|
||||
<TD>
|
||||
<?php $menu[0] = get_string("false", "quiz");
|
||||
$menu[1] = get_string("true", "quiz");
|
||||
choose_from_menu($menu, "answer", "$question->answer", ""); ?>
|
||||
<BR>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("feedback", "quiz") ?> (<?php print_string("true", "quiz") ?>):</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="feedbacktrue" rows=2 cols=50 wrap="virtual"><?php p($true->feedback) ?></textarea>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR valign=top>
|
||||
<TD align=right><P><B><?php print_string("feedback", "quiz") ?> (<?php print_string("false", "quiz") ?>):</B></P></TD>
|
||||
<TD>
|
||||
<textarea name="feedbackfalse" rows=2 cols=50 wrap="virtual"><?php p($false->feedback) ?></textarea>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
|
||||
<INPUT type="hidden" name=id value="<?php p($question->id) ?>">
|
||||
<INPUT type="hidden" name=qtype value="<?php p($question->qtype) ?>">
|
||||
<INPUT type="submit" value="<?php print_string("savechanges") ?>">
|
||||
|
||||
</CENTER>
|
||||
</FORM>
|
||||
<?php
|
||||
if ($usehtmleditor) {
|
||||
print_richedit_javascript("theform", "questiontext", "no");
|
||||
}
|
||||
?>
|
@ -29,12 +29,11 @@ class quiz_report extends quiz_default_report {
|
||||
|
||||
$count->attempt++;
|
||||
|
||||
if (! $questions = quiz_get_attempt_responses($attempt, $quiz)) {
|
||||
if (! $questions = quiz_get_attempt_questions($quiz, $attempt)) {
|
||||
error("Could not reconstruct quiz results for attempt $attempt->id!");
|
||||
}
|
||||
quiz_remove_unwanted_questions($questions, $quiz);
|
||||
|
||||
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
|
||||
if (!$result = quiz_grade_responses($quiz, $questions)) {
|
||||
error("Could not re-grade this quiz attempt!");
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,445 @@
|
||||
|
||||
/// Overview report just displays a big table of all the attempts
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
/// With the refactoring of the quiz module in July-2004, some
|
||||
/// of the functions in lib.php were moved here instead as they
|
||||
/// are no longer in use by the other quiz components.
|
||||
/// These functions are quiz_get_attempt_responses,
|
||||
/// quiz_grade_attempt, quiz_grade_attempt_results,
|
||||
/// quiz_remove_unwanted_questions and quiz_get_answers.
|
||||
/// They were all properly renamed by exchanging quiz_
|
||||
/// with quiz_report_simplestat_
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
function quiz_report_simplestat_get_attempt_responses($attempt) {
|
||||
// Given an attempt object, this function gets all the
|
||||
// stored responses and returns them in a format suitable
|
||||
// for regrading using quiz_grade_attempt_results()
|
||||
global $CFG;
|
||||
|
||||
if (!$responses = get_records_sql("SELECT q.id, q.qtype, q.category, q.questiontext,
|
||||
q.defaultgrade, q.image, r.answer
|
||||
FROM {$CFG->prefix}quiz_responses r,
|
||||
{$CFG->prefix}quiz_questions q
|
||||
WHERE r.attempt = '$attempt->id'
|
||||
AND q.id = r.question")) {
|
||||
notify("Could not find any responses for that attempt!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
foreach ($responses as $key => $response) {
|
||||
if ($response->qtype == RANDOM) {
|
||||
$responses[$key]->random = $response->answer;
|
||||
$responses[$response->answer]->delete = true;
|
||||
|
||||
$realanswer = $responses[$response->answer]->answer;
|
||||
|
||||
if (is_array($realanswer)) {
|
||||
$responses[$key]->answer = $realanswer;
|
||||
} else {
|
||||
$responses[$key]->answer = explode(",", $realanswer);
|
||||
}
|
||||
|
||||
} else if ($response->qtype == NUMERICAL or $response->qtype == SHORTANSWER) {
|
||||
$responses[$key]->answer = array($response->answer);
|
||||
} else {
|
||||
$responses[$key]->answer = explode(",",$response->answer);
|
||||
}
|
||||
}
|
||||
foreach ($responses as $key => $response) {
|
||||
if (!empty($response->delete)) {
|
||||
unset($responses[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $responses;
|
||||
}
|
||||
|
||||
function quiz_report_simplestat_grade_attempt_question_result($question,
|
||||
$answers,
|
||||
$gradecanbenegative= false)
|
||||
{
|
||||
$grade = 0; // default
|
||||
$correct = array();
|
||||
$feedback = array();
|
||||
$response = array();
|
||||
|
||||
switch ($question->qtype) {
|
||||
case SHORTANSWER:
|
||||
if ($question->answer) {
|
||||
$question->answer = trim(stripslashes($question->answer[0]));
|
||||
} else {
|
||||
$question->answer = "";
|
||||
}
|
||||
$response[0] = $question->answer;
|
||||
$feedback[0] = ''; // Default
|
||||
foreach ($answers as $answer) { // There might be multiple right answers
|
||||
|
||||
$answer->answer = trim($answer->answer); // Just in case
|
||||
|
||||
if ($answer->fraction >= 1.0) {
|
||||
$correct[] = $answer->answer;
|
||||
}
|
||||
if (!$answer->usecase) { // Don't compare case
|
||||
$answer->answer = strtolower($answer->answer);
|
||||
$question->answer = strtolower($question->answer);
|
||||
}
|
||||
|
||||
$potentialgrade = (float)$answer->fraction * $question->grade;
|
||||
|
||||
if ($potentialgrade >= $grade and (strpos(' '.$answer->answer, '*'))) {
|
||||
$answer->answer = str_replace('\*','@@@@@@',$answer->answer);
|
||||
$answer->answer = str_replace('*','.*',$answer->answer);
|
||||
$answer->answer = str_replace('@@@@@@', '\*',$answer->answer);
|
||||
$answer->answer = str_replace('+', '\+',$answer->answer);
|
||||
if (eregi('^'.$answer->answer.'$', $question->answer)) {
|
||||
$feedback[0] = $answer->feedback;
|
||||
$grade = $potentialgrade;
|
||||
}
|
||||
|
||||
} else if ($answer->answer == $question->answer) {
|
||||
$feedback[0] = $answer->feedback;
|
||||
$grade = $potentialgrade;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NUMERICAL:
|
||||
if ($question->answer) {
|
||||
$question->answer = trim(stripslashes($question->answer[0]));
|
||||
} else {
|
||||
$question->answer = "";
|
||||
}
|
||||
$response[0] = $question->answer;
|
||||
$bestshortanswer = 0;
|
||||
foreach ($answers as $answer) { // There might be multiple right answers
|
||||
if ($answer->fraction > $bestshortanswer) {
|
||||
$correct[$answer->id] = $answer->answer;
|
||||
$bestshortanswer = $answer->fraction;
|
||||
$feedback[0] = $answer->feedback; // Show feedback for best answer
|
||||
}
|
||||
if ('' != $question->answer // Must not be mixed up with zero!
|
||||
&& (float)$answer->fraction > (float)$grade // Do we need to bother?
|
||||
and // and has lower procedence than && and ||.
|
||||
strtolower($question->answer) == strtolower($answer->answer)
|
||||
|| '' != trim($answer->min)
|
||||
&& ((float)$question->answer >= (float)$answer->min)
|
||||
&& ((float)$question->answer <= (float)$answer->max))
|
||||
{
|
||||
//$feedback[0] = $answer->feedback; No feedback was shown for wrong answers
|
||||
$grade = (float)$answer->fraction;
|
||||
}
|
||||
}
|
||||
$grade *= $question->grade; // Normalize to correct weight
|
||||
break;
|
||||
|
||||
case TRUEFALSE:
|
||||
if ($question->answer) {
|
||||
$question->answer = $question->answer[0];
|
||||
} else {
|
||||
$question->answer = NULL;
|
||||
}
|
||||
foreach($answers as $answer) { // There should be two answers (true and false)
|
||||
$feedback[$answer->id] = $answer->feedback;
|
||||
if ($answer->fraction > 0) {
|
||||
$correct[$answer->id] = true;
|
||||
}
|
||||
if ($question->answer == $answer->id) {
|
||||
$grade = (float)$answer->fraction * $question->grade;
|
||||
$response[$answer->id] = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case MULTICHOICE:
|
||||
foreach($answers as $answer) { // There will be multiple answers, perhaps more than one is right
|
||||
$feedback[$answer->id] = $answer->feedback;
|
||||
if ($answer->fraction > 0) {
|
||||
$correct[$answer->id] = true;
|
||||
}
|
||||
if (!empty($question->answer)) {
|
||||
foreach ($question->answer as $questionanswer) {
|
||||
if ($questionanswer == $answer->id) {
|
||||
$response[$answer->id] = true;
|
||||
if ($answer->single) {
|
||||
$grade = (float)$answer->fraction * $question->grade;
|
||||
continue;
|
||||
} else {
|
||||
$grade += (float)$answer->fraction * $question->grade;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MATCH:
|
||||
$matchcount = $totalcount = 0;
|
||||
|
||||
foreach ($question->answer as $questionanswer) { // Each answer is "subquestionid-answerid"
|
||||
$totalcount++;
|
||||
$qarr = explode('-', $questionanswer); // Extract subquestion/answer.
|
||||
$subquestionid = $qarr[0];
|
||||
$subanswerid = $qarr[1];
|
||||
if ($subquestionid and $subanswerid and (($subquestionid == $subanswerid) or
|
||||
($answers[$subquestionid]->answertext == $answers[$subanswerid]->answertext))) {
|
||||
// Either the ids match exactly, or the answertexts match exactly
|
||||
// (in case two subquestions had the same answer)
|
||||
$matchcount++;
|
||||
$correct[$subquestionid] = true;
|
||||
} else {
|
||||
$correct[$subquestionid] = false;
|
||||
}
|
||||
$response[$subquestionid] = $subanswerid;
|
||||
}
|
||||
|
||||
$grade = $question->grade * $matchcount / $totalcount;
|
||||
|
||||
break;
|
||||
|
||||
case RANDOMSAMATCH:
|
||||
$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 * $question->grade * $answerfraction;
|
||||
}
|
||||
}
|
||||
}
|
||||
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_report_simplestat_grade_attempt_question_result($subquestion, $multianswer->subanswers, true);
|
||||
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;
|
||||
|
||||
case RANDOM: // Returns a recursive call with the real question
|
||||
$realquestion = get_record
|
||||
('quiz_questions', 'id', $question->random);
|
||||
$realquestion->answer = $question->answer;
|
||||
$realquestion->grade = $question->grade;
|
||||
return quiz_report_simplestat_grade_attempt_question_result($realquestion, $answers);
|
||||
}
|
||||
|
||||
$result->grade =
|
||||
$gradecanbenegative ? $grade // Grade can be negative
|
||||
: max(0.0, $grade); // Grade must not be negative
|
||||
$result->correct = $correct;
|
||||
$result->feedback = $feedback;
|
||||
$result->response = $response;
|
||||
return $result;
|
||||
}
|
||||
|
||||
function quiz_report_simplestat_remove_unwanted_questions(&$questions, $quiz) {
|
||||
/// Given an array of questions, and a list of question IDs,
|
||||
/// this function removes unwanted questions from the array
|
||||
/// Used by review.php and attempt.php to counter changing quizzes
|
||||
|
||||
$quizquestions = array();
|
||||
$quizids = explode(",", $quiz->questions);
|
||||
foreach ($quizids as $quizid) {
|
||||
$quizquestions[$quizid] = true;
|
||||
}
|
||||
foreach ($questions as $key => $question) {
|
||||
if (!isset($quizquestions[$question->id])) {
|
||||
unset($questions[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function quiz_report_simplestat_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 "
|
||||
. $answeridconstraint);
|
||||
|
||||
case TRUEFALSE: // Should be always two answers
|
||||
return get_records("quiz_answers", "question", $question->id);
|
||||
|
||||
case MULTICHOICE: // Should be multiple answers
|
||||
return get_records_sql("SELECT a.*, mc.single
|
||||
FROM {$CFG->prefix}quiz_multichoice mc,
|
||||
{$CFG->prefix}quiz_answers a
|
||||
WHERE mc.question = '$question->id'
|
||||
AND mc.question = a.question "
|
||||
. $answeridconstraint);
|
||||
|
||||
case MATCH:
|
||||
return get_records("quiz_match_sub", "question", $question->id);
|
||||
|
||||
case RANDOMSAMATCH: // Could be any of many answers, return them all
|
||||
return get_records_sql("SELECT a.*
|
||||
FROM {$CFG->prefix}quiz_questions q,
|
||||
{$CFG->prefix}quiz_answers a
|
||||
WHERE q.category = '$question->category'
|
||||
AND q.qtype = ".SHORTANSWER."
|
||||
AND q.id = a.question ");
|
||||
|
||||
case NUMERICAL: // Logical support for multiple answers
|
||||
return get_records_sql("SELECT a.*, n.min, n.max
|
||||
FROM {$CFG->prefix}quiz_numerical n,
|
||||
{$CFG->prefix}quiz_answers a
|
||||
WHERE a.question = '$question->id'
|
||||
AND n.answer = a.id "
|
||||
. $answeridconstraint);
|
||||
|
||||
case DESCRIPTION:
|
||||
return true; // there are no answers for description
|
||||
|
||||
case RANDOM:
|
||||
return quiz_get_answers
|
||||
(get_record('quiz_questions', 'id', $question->random));
|
||||
|
||||
case MULTIANSWER: // Includes subanswers
|
||||
$answers = array();
|
||||
|
||||
$virtualquestion->id = $question->id;
|
||||
|
||||
if ($multianswers = get_records('quiz_multianswers', 'question', $question->id)) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function quiz_report_simplestat_grade_attempt_results($quiz, $questions) {
|
||||
/// Given a list of questions (including answers for each one)
|
||||
/// this function does all the hard work of calculating the
|
||||
/// grades for each question, as well as a total grade for
|
||||
/// for the whole quiz. It returns everything in a structure
|
||||
/// that looks like:
|
||||
/// $result->sumgrades (sum of all grades for all questions)
|
||||
/// $result->percentage (Percentage of grades that were correct)
|
||||
/// $result->grade (final grade result for the whole quiz)
|
||||
/// $result->grades[] (array of grades, indexed by question id)
|
||||
/// $result->response[] (array of response arrays, indexed by question id)
|
||||
/// $result->feedback[] (array of feedback arrays, indexed by question id)
|
||||
/// $result->correct[] (array of feedback arrays, indexed by question id)
|
||||
|
||||
if (!$questions) {
|
||||
error("No questions!");
|
||||
}
|
||||
|
||||
if (!$grades = get_records_menu("quiz_question_grades", "quiz", $quiz->id, "", "question,grade")) {
|
||||
error("No grades defined for these quiz questions!");
|
||||
}
|
||||
|
||||
$result->sumgrades = 0;
|
||||
|
||||
foreach ($questions as $question) {
|
||||
|
||||
$question->grade = $grades[$question->id];
|
||||
|
||||
if (!$answers = quiz_report_simplestat_get_answers($question)) {
|
||||
error("No answers defined for question id $question->id!");
|
||||
}
|
||||
|
||||
$questionresult = quiz_report_simplestat_grade_attempt_question_result($question,
|
||||
$answers);
|
||||
// if time limit is enabled and exceeded, return zero grades
|
||||
if($quiz->timelimit > 0) {
|
||||
if(($quiz->timelimit + 60) <= $quiz->timesincestart) {
|
||||
$questionresult->grade = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$result->grades[$question->id] = round($questionresult->grade, 2);
|
||||
$result->sumgrades += $questionresult->grade;
|
||||
$result->feedback[$question->id] = $questionresult->feedback;
|
||||
$result->response[$question->id] = $questionresult->response;
|
||||
$result->correct[$question->id] = $questionresult->correct;
|
||||
}
|
||||
|
||||
$fraction = (float)($result->sumgrades / $quiz->sumgrades);
|
||||
$result->percentage = format_float($fraction * 100.0);
|
||||
$result->grade = format_float($fraction * $quiz->grade);
|
||||
$result->sumgrades = round($result->sumgrades, 2);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
class quiz_report extends quiz_default_report {
|
||||
|
||||
function display($quiz, $cm, $course) { /// This function just displays the report
|
||||
@ -48,12 +487,12 @@ class quiz_report extends quiz_default_report {
|
||||
if (!$bestattempt = quiz_calculate_best_attempt($quiz, $attempts)) {
|
||||
continue;
|
||||
}
|
||||
if (!$questions = quiz_get_attempt_responses($bestattempt, $quiz)) {
|
||||
if (!$questions = quiz_report_simplestat_get_attempt_responses($bestattempt, $quiz)) {
|
||||
continue;
|
||||
}
|
||||
quiz_remove_unwanted_questions($questions, $quiz);
|
||||
quiz_report_simplestat_remove_unwanted_questions($questions, $quiz);
|
||||
|
||||
if (!$results = quiz_grade_attempt_results($quiz, $questions)) {
|
||||
if (!$results = quiz_report_simplestat_grade_attempt_results($quiz, $questions)) {
|
||||
error("Could not re-grade this quiz attempt!");
|
||||
}
|
||||
|
||||
|
@ -85,20 +85,11 @@
|
||||
print_heading($quiz->name);
|
||||
|
||||
|
||||
if (! $questions = quiz_get_attempt_responses($attempt)) {
|
||||
if ($user = get_record("user", "id", $attempt->userid)) {
|
||||
$fullname = fullname($user);
|
||||
} else {
|
||||
$fullname = "????";
|
||||
}
|
||||
print_heading(get_string("attemptincomplete", "quiz", $fullname));
|
||||
print_footer($course);
|
||||
exit;
|
||||
if (!($questions = quiz_get_attempt_questions($quiz, $attempt))) {
|
||||
error("Unable to get questions from database for quiz $quiz->id attempt $attempt->id number $attempt->attempt");
|
||||
}
|
||||
|
||||
quiz_remove_unwanted_questions($questions, $quiz);
|
||||
|
||||
if (!$result = quiz_grade_attempt_results($quiz, $questions)) {
|
||||
if (!$result = quiz_grade_responses($quiz, $questions)) {
|
||||
error("Could not re-grade this quiz attempt!");
|
||||
}
|
||||
|
||||
@ -128,7 +119,7 @@
|
||||
$quiz->correctanswers = true;
|
||||
$quiz->shuffleanswers = false;
|
||||
$quiz->shufflequestions = false;
|
||||
quiz_print_quiz_questions($quiz, $result, $questions);
|
||||
quiz_print_quiz_questions($quiz, $questions, $result);
|
||||
|
||||
if (isteacher($course->id)) {
|
||||
print_continue("report.php?q=$quiz->id");
|
||||
|