diff --git a/mod/quiz/attempt.php b/mod/quiz/attempt.php
index 96af7ea6f9a..be687bdcf5f 100644
--- a/mod/quiz/attempt.php
+++ b/mod/quiz/attempt.php
@@ -216,13 +216,13 @@
// list of questions needed by page
$pagelist = quiz_questions_on_page($attempt->layout, $page);
- // add all questions that are on the submitted form
if ($newattempt) {
- $questionlist = $attempt->layout;
+ $questionlist = quiz_questions_in_quiz($attempt->layout);
} else {
$questionlist = $pagelist;
}
+ // add all questions that are on the submitted form
if ($questionids) {
$questionlist .= ','.$questionids;
}
@@ -253,6 +253,13 @@
error('Could not restore question sessions');
}
+ // Save all the newly created states
+ if ($newattempt) {
+ foreach ($questions as $i => $question) {
+ quiz_save_question_session($questions[$i], $states[$i]);
+ }
+ }
+
/// Process form data /////////////////////////////////////////////////
@@ -370,13 +377,16 @@
/// Print the attempt number or preview heading
if ($isteacher) {
- print_heading(get_string('previewquiz', 'quiz'));
+ print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name)));
unset($buttonoptions);
$buttonoptions['q'] = $quiz->id;
$buttonoptions['forcenew'] = true;
echo '
\n";
+ echo " \n";
$sumgrades = quiz_print_question_list($modform, false, $SESSION->quiz_showbreaks);
if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $modform->instance)) {
diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php
index b06b99f0db1..896d170e742 100644
--- a/mod/quiz/locallib.php
+++ b/mod/quiz/locallib.php
@@ -247,7 +247,7 @@ class quiz_default_questiontype {
// The default implementation attaches all answers for this question
if (!$question->options->answers = get_records('quiz_answers', 'question',
$question->id)) {
- notify('Error: Missing question answers!');
+ //notify('Error: Missing question answers!');
return false;
}
return true;
@@ -1349,39 +1349,43 @@ function quiz_save_question_session(&$question, &$state) {
$state->answer = isset($state->responses['']) ? $state->responses[''] : '';
// Save the state
- unset($state->id);
- if (!$state->id = insert_record('quiz_states', $state)) {
- unset($state->id);
- unset($state->answer);
- return false;
- }
- unset($state->answer);
-
- // this is the most recent state
- if (!record_exists('quiz_newest_states', 'attemptid',
- $state->attempt, 'questionid', $question->id)) {
- $new->attemptid = $state->attempt;
- $new->questionid = $question->id;
- $new->newest = $state->id;
- $new->newgraded = $state->id;
- $new->sumpenalty = $state->sumpenalty;
- if (!insert_record('quiz_newest_states', $new)) {
- error('Could not insert entry in quiz_newest_states');
- }
+ if (isset($state->update)) {
+ update_record('quiz_states', $state);
} else {
- set_field('quiz_newest_states', 'newest', $state->id, 'attemptid',
- $state->attempt, 'questionid', $question->id);
- }
- if (quiz_state_is_graded($state)) {
- // this is also the most recent graded state
- if ($newest = get_record('quiz_newest_states', 'attemptid',
+ if (!$state->id = insert_record('quiz_states', $state)) {
+ unset($state->id);
+ unset($state->answer);
+ return false;
+ }
+
+ // this is the most recent state
+ if (!record_exists('quiz_newest_states', 'attemptid',
$state->attempt, 'questionid', $question->id)) {
- $newest->newgraded = $state->id;
- $newest->sumpenalty = $state->sumpenalty;
- update_record('quiz_newest_states', $newest);
+ $new->attemptid = $state->attempt;
+ $new->questionid = $question->id;
+ $new->newest = $state->id;
+ $new->newgraded = $state->id;
+ $new->sumpenalty = $state->sumpenalty;
+ if (!insert_record('quiz_newest_states', $new)) {
+ error('Could not insert entry in quiz_newest_states');
+ }
+ } else {
+ set_field('quiz_newest_states', 'newest', $state->id, 'attemptid',
+ $state->attempt, 'questionid', $question->id);
+ }
+ if (quiz_state_is_graded($state)) {
+ // this is also the most recent graded state
+ if ($newest = get_record('quiz_newest_states', 'attemptid',
+ $state->attempt, 'questionid', $question->id)) {
+ $newest->newgraded = $state->id;
+ $newest->sumpenalty = $state->sumpenalty;
+ update_record('quiz_newest_states', $newest);
+ }
}
}
+ unset($state->answer);
+
// Save the question type specific state information and responses
if (!$QUIZ_QTYPES[$question->qtype]->save_session_and_responses(
$question, $state)) {
@@ -1465,98 +1469,92 @@ function quiz_extract_responses($questions, $responses, $defaultevent) {
}
+
/**
-* For a given question instance we walk the complete history of states for
-* each user and recalculate the grades as we go along.
+* For a given question in an attempt we walk the complete history of states
+* and recalculate the grades as we go along.
*
* This is used when a question in an existing quiz is changed and old student
* responses need to be marked with the new version of a question.
*
* TODO: Finish documenting this
* @return boolean Indicates success/failure
-* @param object $question A question object
-* @param array $quizlist An array of quiz ids, in which the question should
-* be regraded. If quizlist == 'all' all quizzes are affected
+* @param object $question A question object
+* @param object $attempt The attempt, in which the question needs to be regraded.
+* @param object $quiz Optional. The quiz object that the attempt corresponds to.
+* @param boolean $verbose Optional. Whether to print progress information or not.
*/
-function quiz_regrade_question_in_quizzes($question, $quizlist) {
-
- // Disable until tested
- return;
-
- if (empty($quizlist)) {
- return;
+function quiz_regrade_question_in_attempt($question, $attempt, $quiz=false, $verbose=false) {
+ if (!$quiz && !($quiz = get_record('quiz', 'id', $attempt->quiz))) {
+ $verbose && notify("Regrading of quiz #{$attempt->quiz} failed; " .
+ "Couldn't load quiz record from database!");
+ return false;
}
- if ($quizlist == 'all') { // assume that all quizzes are affected
- // fetch a list of all the quizzes using this question
- if (! $instances = (array)get_records('quiz_question_instances',
- 'question', $question->id, '', 'id, quiz')) {
- // No instances were found, so it successfully regraded all of them
- return true;
- }
- $quizlist = array_map(create_function('$val', 'return $val->quiz;'), $instances);
- unset($instances);
- }
+ if ($states = get_records_select('quiz_states',
+ "attempt = '{$attempt->id}' AND question = '{$question->id}'", 'seq_number ASC')) {
+ $states = array_values($states);
- // Get all affected quizzes
- $quizlist = implode(',', $quizlist);
- if (! $quizzes = get_records_list('quiz', 'id', $quizlist)) {
- error('Couldn\'t get quizzes for regrading!');
- }
+ $attempt->sumgrades -= $states[count($states)-1]->grade;
- foreach ($quizzes as $quiz) {
- // All the attempts that need to be changed
- if (! $attempts = get_records('quiz_attempts', 'quiz', $quiz->id)) {
- continue;
- }
- $attempts = array_values($attempts);
- if (! $instance = get_record('quiz_question_instances',
- 'quiz', $quiz->id, 'question', $question->id)) {
- error("Couldn't get question instance for regrading!");
- }
- $question->maxgrade = $instance->grade;
- for ($i = 0; $i < count($attempts); $i++) {
- if ($states = get_records_select('quiz_states',
- "attempt = '{$attempts[$i]->id}' ".
- "AND question = '{$question->id}'",
- 'seq_number ASC')) {
- $states = array_values($states);
+ // Initialise the replaystate
+ $state = clone($states[0]);
+ quiz_restore_state($question, $state);
+ $state->sumpenalty = 0.0;
+ $state->raw_grade = 0;
+ $state->grade = 0;
+ $state->responses = array(''=>'');
+ $state->event = QUIZ_EVENTOPEN;
+ $replaystate = clone($state);
+ $replaystate->last_graded = $state;
- $attempts[$i]->sumgrades -= $states[count($states)-1]->grade;
+ $changed = 0;
+ for($j = 0; $j < count($states); $j++) {
+ quiz_restore_state($question, $states[$j]);
+ $action = new stdClass;
+ $action->responses = $states[$j]->responses;
+ $action->timestamp = $states[$j]->timestamp;
- // Initialise the replaystate
- quiz_restore_state($question, $states[0]);
- $replaystate = clone($states[0]);
- $replaystate->last_graded = clone($states[0]);
- for($j = 1; $j < count($states); $j++) {
- quiz_restore_state($question, $states[$j]);
- $action = new stdClass;
- $action->responses = $states[$j]->responses;
- // Close the last state of a finished attempt
- if (((count($states) - 1) === $j) && ($attempts[$i]->timefinish > 0)) {
- $action->event = QUIZ_EVENTCLOSE;
+ // Close the last state of a finished attempt
+ if (((count($states) - 1) === $j) && ($attempt->timefinish > 0)) {
+ $action->event = QUIZ_EVENTCLOSE;
- // Grade instead of closing, quiz_process_responses will then
- // work out whether to close it
- } else if (QUIZ_EVENTCLOSE == $states[$j]->event) {
- $action->event = QUIZ_EVENTGRADE;
+ // Grade instead of closing, quiz_process_responses will then
+ // work out whether to close it
+ } else if (QUIZ_EVENTCLOSE == $states[$j]->event) {
+ $action->event = QUIZ_EVENTGRADE;
- // By default take the event that was saved in the database
- } else {
- $action->event = $states[$j]->event;
- }
-
- // Reprocess (regrade) responses
- quiz_process_responses($question, $replaystate, $action, $quiz,
- $attempts[$i]);
- $replaystate->id = $states[$j]->id;
- update_record('quiz_states', $replaystate);
- }
- update_record('quiz_attempts', $attempts[$i]);
- quiz_save_best_grade($quiz, $attempts[$i]->userid);
+ // By default take the event that was saved in the database
+ } else {
+ $action->event = $states[$j]->event;
}
+ // Reprocess (regrade) responses
+ if (!quiz_process_responses($question, $replaystate, $action, $quiz,
+ $attempt)) {
+ $verbose && notify("Couldn't regrade state #{$state->id}!");
+ }
+ if ((float)$replaystate->raw_grade != (float)$states[$j]->raw_grade) {
+ $changed++;
+
+ }
+ $replaystate->id = $states[$j]->id;
+ $replaystate->update = true;
+ quiz_save_question_session($question, $replaystate);
}
+ if ($verbose) {
+ if ($changed) {
+ link_to_popup_window ('/mod/quiz/reviewquestion.php?attempt='.$attempt->id.'&question='.$question->id,
+ 'reviewquestion', ' #'.$attempt->id, 450, 550, get_string('reviewresponse', 'quiz'));
+ update_record('quiz_attempts', $attempt);
+ } else {
+ echo ' #'.$attempt->id;
+ }
+ echo "\n"; flush(); ob_flush();
+ }
+
+ return true;
}
+ return true;
}
/**
@@ -2244,7 +2242,7 @@ if (!$grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', $userid)) {
}
/**
-* TODO: document this
+* Save the overall grade for a user at a quiz in the quiz_grades table
*
* @return boolean Indicates success or failure.
* @param object $quiz The quiz for which the best grade is to be calculated
@@ -2269,11 +2267,12 @@ function quiz_save_best_grade($quiz, $userid=null) {
// Calculate the best grade
$bestgrade = quiz_calculate_best_grade($quiz, $attempts);
$bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
+ $bestgrade = round($bestgrade, $quiz->decimalpoints);
// Save the best grade in the database
if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid',
$userid)) {
- $grade->grade = round($bestgrade, $quiz->decimalpoints);
+ $grade->grade = $bestgrade;
$grade->timemodified = time();
if (!update_record('quiz_grades', $grade)) {
notify('Could not update best grade');
@@ -2282,7 +2281,7 @@ function quiz_save_best_grade($quiz, $userid=null) {
} else {
$grade->quiz = $quiz->id;
$grade->userid = $userid;
- $grade->grade = round($bestgrade, $quiz->decimalpoints);
+ $grade->grade = $bestgrade;
$grade->timemodified = time();
if (!insert_record('quiz_grades', $grade)) {
notify('Could not insert new best grade');
@@ -2292,9 +2291,14 @@ function quiz_save_best_grade($quiz, $userid=null) {
return true;
}
-
+/**
+* Calculate the overall grade for a quiz given a number of attempts by a particular user.
+*
+* @return float The overall grade
+* @param object $quiz The quiz for which the best grade is to be calculated
+* @param array $attempts An array of all the attempts of the user at the quiz
+*/
function quiz_calculate_best_grade($quiz, $attempts) {
-/// Calculate the best grade for a quiz given a number of attempts by a particular user.
switch ($quiz->grademethod) {
@@ -2331,9 +2335,16 @@ function quiz_calculate_best_grade($quiz, $attempts) {
}
}
-
+/**
+* Return the attempt with the best grade for a quiz
+*
+* Which attempt is the best depends on $quiz->grademethod. If the grade
+* method is GRADEAVERAGE then this function simply returns the last attempt.
+* @return object The attempt with the best grade
+* @param object $quiz The quiz for which the best grade is to be calculated
+* @param array $attempts An array of all the attempts of the user at the quiz
+*/
function quiz_calculate_best_attempt($quiz, $attempts) {
-/// Return the attempt with the best grade for a quiz
switch ($quiz->grademethod) {
diff --git a/mod/quiz/question.php b/mod/quiz/question.php
index d1b8ca1b126..a01836c9eda 100644
--- a/mod/quiz/question.php
+++ b/mod/quiz/question.php
@@ -256,7 +256,7 @@
set_field('quiz_newest_states', 'questionid', $question->id, 'attemptid', $attempt->id, 'questionid', $oldquestionid);
}
-
+
// Now do anything question-type specific that is required to replace the question
// For example questions that use the quiz_answers table to hold part of their question will
// have to recode the answer ids in the states
@@ -272,11 +272,12 @@
}
if (empty($question->errors) && $QUIZ_QTYPES[$qtype]->finished_edit_wizard($form)) {
+ // DISABLED AUTOMATIC REGRADING
// Automagically regrade all attempts (and states) in the affected quizzes
- if (!empty($replaceinquiz)) {
- $QUIZ_QTYPES[$question->qtype]->get_question_options($question);
- quiz_regrade_question_in_quizzes($question, $replaceinquiz);
- }
+ //if (!empty($replaceinquiz)) {
+ // $QUIZ_QTYPES[$question->qtype]->get_question_options($question);
+ // quiz_regrade_question_in_quizzes($question, $replaceinquiz);
+ //}
redirect("edit.php");
}
}
diff --git a/mod/quiz/questiontypes/calculated/questiontype.php b/mod/quiz/questiontypes/calculated/questiontype.php
index d2d412b9af6..601751eb915 100644
--- a/mod/quiz/questiontypes/calculated/questiontype.php
+++ b/mod/quiz/questiontypes/calculated/questiontype.php
@@ -368,7 +368,8 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
}
// Get answers
- $answers = $question->options->answers;
+ // the next line is hacked to get rid of the PHP notice until this gets fixed properly
+ $answers = (isset($question->options->answers)) ? $question->options->answers : null;
$stranswers = get_string('answer', 'quiz');
$strmin = get_string('min', 'quiz');
$strmax = get_string('max', 'quiz');
@@ -378,26 +379,28 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
$state->responses = array();
$state->options = new stdClass;
$virtualqtype = $this->get_virtual_qtype();
- foreach ($answers as $answer) {
- $calculated = quiz_qtype_calculated_calculate_answer(
- $answer->answer, $data, $answer->tolerance,
- $answer->tolerancetype, $answer->correctanswerlength,
- $answer->correctanswerformat, $unit);
- $state->responses[''] = $calculated->answer;
- $virtualqtype->get_tolerance_interval($question, $state);
- $calculated->min = $state->options->min;
- $calculated->max = $state->options->max;
- if ($calculated->min === '') {
- // This should mean that something is wrong
- $errors .= " -$calculated->answer";
- $stranswers .= $delimiter;
- } else {
- $stranswers .= $delimiter.$calculated->answer;
+ if ($answers) {
+ foreach ($answers as $answer) {
+ $calculated = quiz_qtype_calculated_calculate_answer(
+ $answer->answer, $data, $answer->tolerance,
+ $answer->tolerancetype, $answer->correctanswerlength,
+ $answer->correctanswerformat, $unit);
+ $state->responses[''] = $calculated->answer;
+ $virtualqtype->get_tolerance_interval($question, $state);
+ $calculated->min = $state->options->min;
+ $calculated->max = $state->options->max;
+ if ($calculated->min === '') {
+ // This should mean that something is wrong
+ $errors .= " -$calculated->answer";
+ $stranswers .= $delimiter;
+ } else {
+ $stranswers .= $delimiter.$calculated->answer;
+ }
+ $strmin .= $delimiter.$calculated->min;
+ $strmax .= $delimiter.$calculated->max;
+
+ $delimiter = ', ';
}
- $strmin .= $delimiter.$calculated->min;
- $strmax .= $delimiter.$calculated->max;
-
- $delimiter = ', ';
}
return "$stranswers $strmin $strmax $errors";
}
diff --git a/mod/quiz/questiontypes/multichoice/questiontype.php b/mod/quiz/questiontypes/multichoice/questiontype.php
index 76594fdd84b..0f8572dc53b 100644
--- a/mod/quiz/questiontypes/multichoice/questiontype.php
+++ b/mod/quiz/questiontypes/multichoice/questiontype.php
@@ -239,7 +239,7 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
- $readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
+ $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$formatoptions = new stdClass;
$formatoptions->para = false;
diff --git a/mod/quiz/questiontypes/numerical/questiontype.php b/mod/quiz/questiontypes/numerical/questiontype.php
index 6355211e64b..a52ac3648ee 100644
--- a/mod/quiz/questiontypes/numerical/questiontype.php
+++ b/mod/quiz/questiontypes/numerical/questiontype.php
@@ -246,7 +246,7 @@ class quiz_numerical_qtype extends quiz_shortanswer_qtype {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
- $readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
+ $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$nameprefix = $question->name_prefix;
/// Print question text and media
diff --git a/mod/quiz/questiontypes/random/questiontype.php b/mod/quiz/questiontypes/random/questiontype.php
index 6bbf047e99a..0ce8bc1fdd5 100644
--- a/mod/quiz/questiontypes/random/questiontype.php
+++ b/mod/quiz/questiontypes/random/questiontype.php
@@ -7,13 +7,7 @@
/// 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);
+ var $excludedtypes = array(RANDOM, RANDOMSAMATCH);
// Carries questions available as randoms sorted by category
// This array is used when needed only
@@ -52,8 +46,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
// Need to fetch random questions from category $question->category"
// (Note: $this refers to the questiontype, not the question.)
global $CFG;
- $possiblerandomqtypes = "'"
- . implode("','", $this->possiblerandomqtypes) . "'";
+ $excludedtypes = implode(',', $this->excludedtypes);
if ($question->questiontext == "1") {
// recurse into subcategories
$categorylist = quiz_categorylist($question->category);
@@ -65,7 +58,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
WHERE category IN ($categorylist)
AND parent = '0'
AND id NOT IN ($quiz->questionsinuse)
- AND qtype IN ($possiblerandomqtypes)");
+ AND qtype NOT IN ($excludedtypes)");
$this->catrandoms[$question->category] =
draw_rand_array($this->catrandoms[$question->category],
count($this->catrandoms[$question->category])); // from bug 1889
@@ -80,13 +73,6 @@ class quiz_random_qtype extends quiz_default_questiontype {
global $QUIZ_QTYPES;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion);
-
- // Backup the original state of the random question
- // And change the $state to match the wrapped question. This
- // is sensible, because so the wrapped question's state gets
- // put through all the generic processing.
- $state->options->state = clone($state);
- $state->question = $wrappedquestion->id;
$QUIZ_QTYPES[$wrappedquestion->qtype]
->create_session_and_responses($wrappedquestion,
$state, $quiz, $attempt);
@@ -97,38 +83,53 @@ class quiz_random_qtype extends quiz_default_questiontype {
return true;
}
}
- notify(get_string('toomanyrandom', 'quiz', $question->category));
- return false;
+ $question->questiontext = ''.
+ get_string('toomanyrandom', 'quiz', $question->category). '';
+ $question->qtype = DESCRIPTION;
+ $state->responses = array('' => '');
+ return true;
}
function restore_session_and_responses(&$question, &$state) {
+ /// The raw response records for random questions come in two flavours:
+ /// ---- 1 ----
+ /// For responses stored by Moodle version 1.5 and later the answer
+ /// field has the pattern random#-* where the # part is the numeric
+ /// question id of the actual question shown in the quiz attempt
+ /// and * represents the student response to that actual question.
+ /// ---- 2 ----
+ /// For responses stored by older Moodle versions - the answer field is
+ /// simply the question id of the actual question. The student response
+ /// to the actual question is stored in a separate response record.
+ /// -----------------------
+ /// This means that prior to Moodle version 1.5, random questions needed
+ /// two response records for storing the response to a single question.
+ /// From version 1.5 and later the question type random works like all
+ /// the other question types in that it now only needs one response
+ /// record per question.
global $QUIZ_QTYPES;
- if(!$randomstate = get_record('quiz_states', 'question',
- $question->id, 'attempt', $state->attempt)) {
- return false;
+ if (!ereg('^random([0-9]+)-(.*)$', $state->responses[''], $answerregs)) {
+ // this must be an old-style state which stores only the id for the wrapped question
+ if (!$wrappedquestion = get_record('quiz_questions', 'id', $state->responses[''])) {
+ error("Can not find wrapped question $state->responses['']");
+ }
+ // In the old model the actual response was stored in a separate entry in
+ // the state table
+ if (!$state->responses[''] = get_field('quiz_states', 'answer', 'attempt', $state->attempt, 'question', $wrappedquestion->id)) {
+ error("Wrapped state missing");
+ }
+ } else {
+ if (!$wrappedquestion = get_record('quiz_questions', 'id', $answerregs[1])) {
+ return false;
+ }
+ $state->responses[''] = $answerregs[2];
}
- if (!$wrappedquestion = get_record('quiz_questions', 'id',
- $randomstate->answer)) {
- return false;
- }
- $state->question = $wrappedquestion->id;
-
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion)) {
return false;
}
- // We need to set responses[''] to whatever was saved in the most recent
- // state of the wrapped question.
- if(!$wrappedstates = get_records_select('quiz_states',
- "question = $wrappedquestion->id AND attempt = $state->attempt",
- 'seq_number DESC')) {
- return false;
- }
- $wrappedstates = array_values($wrappedstates);
- $state->responses = array('' => $wrappedstates[0]->answer);
-
if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->restore_session_and_responses($wrappedquestion, $state)) {
return false;
@@ -136,21 +137,12 @@ class quiz_random_qtype extends quiz_default_questiontype {
$wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade;
$state->options->question = &$wrappedquestion;
- $state->options->state = &$randomstate;
return true;
}
function save_session_and_responses(&$question, &$state) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
- $randomstate = &$state->options->state;
-
- // We need to save the randomstate manually, because we can only process
- // one response record automatically
- if (empty($randomstate->id)) {
- $randomstate->answer = $wrappedquestion->id;
- $randomstate->id = insert_record('quiz_states', $randomstate);
- }
// Trick the wrapped question into pretending to be the random one.
$realqid = $wrappedquestion->id;
@@ -158,6 +150,21 @@ class quiz_random_qtype extends quiz_default_questiontype {
$QUIZ_QTYPES[$wrappedquestion->qtype]
->save_session_and_responses($wrappedquestion, $state);
+ // Read what the wrapped question has just set the answer field to
+ // (if anything)
+ $response = get_field('quiz_states', 'answer', 'id', $state->id);
+ if(false === $response) {
+ return false;
+ }
+
+ // Prefix the answer field...
+ $response = "random$realqid-$response";
+
+ // ... and save it again.
+ if (!set_field('quiz_states', 'answer', $response, 'id', $state->id)) {
+ return false;
+ }
+
// Restore the real id
$wrappedquestion->id = $realqid;
return true;
@@ -193,34 +200,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
$QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question($wrappedquestion, $state, $number, $quiz, $options);
}
-/*
- function print_question_grading_details(&$question, &$state, $quiz,
- $options) {
- global $QUIZ_QTYPES;
- $wrappedquestion = &$state->options->question;
- $QUIZ_QTYPES[$wrappedquestion->qtype]
- ->print_question_grading_details($wrappedquestion, $state, $quiz,
- $options);
- }
- function print_question_formulation_and_controls(&$question, &$state, $quiz,
- $options) {
- global $QUIZ_QTYPES;
- $wrappedquestion = &$state->options->question;
- $QUIZ_QTYPES[$wrappedquestion->qtype]
- ->print_question_formulation_and_controls($wrappedquestion, $state,
- $quiz, $options);
- }
-
- function print_question_submit_buttons(&$question, &$state, $quiz,
- $options) {
- global $QUIZ_QTYPES;
- $wrappedquestion = &$state->options->question;
- $QUIZ_QTYPES[$wrappedquestion->qtype]
- ->print_question_submit_buttons($wrappedquestion, $state, $quiz,
- $options);
- }
-*/
function grade_responses(&$question, &$state, $quiz) {
global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question;
@@ -255,173 +235,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
return $QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_form_end($wrappedquestion, $state, $quizid);
}
-/*
- function convert_to_response_answer_field($questionresponse) {
- 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.
-
-
- }
-
-
-*/
- /*
- 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 '';
- return $QUIZ_QTYPES[$actualquestion->qtype]
- ->print_question_formulation_and_controls($actualquestion,
- $quiz, $readonly, $answers, $correctanswers,
- quiz_qtype_nameprefix($actualquestion, $nameprefix));
- } else {
- echo '
' . get_string('random', 'quiz') . '
';
- }
- }
-
-
- function get_wrapped_question($question, $nameprefix) {
- if (!empty($question->response[$nameprefix])
- and $actualquestion = get_record('quiz_questions',
- 'id', $question->response[$nameprefix])) {
- $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;
- }
- }
-
-*/
-
- function extract_response($rawresponse, $nameprefix) {
- global $QUIZ_QTYPES;
-
- /// The raw response records for random questions come in two flavours:
- /// ---- 1 ----
- /// For responses stored by Moodle version 1.5 and later the answer
- /// field has the pattern random#-* where the # part is the numeric
- /// question id of the actual question shown in the quiz attempt
- /// and * represents the student response to that actual question.
- /// ---- 2 ----
- /// For responses stored by older Moodle versions - the answer field is
- /// simply the question id of the actual question. The student response
- /// to the actual question is stored in a separate response record.
- /// -----------------------
- /// This means that prior to Moodle version 1.5, random questions needed
- /// two response records for storing the response to a single question.
- /// From version 1.5 and later the question type random works like all
- /// the other question types in that it now only needs one response
- /// record per question.
- /// Because updating the old response records to fit the new response
- /// record format could need hours of CPU time and the equivalent
- /// amount of down time for the Moodle site and because a response
- /// storage with two response formats for random question only effect
- /// this function, where the response record is translated, this
- /// function is now able to handle both types of response record.
-
-
- // Pick random question id from the answer field in a way that
- /// works for both formats:
- if (!ereg('^(random)?([0-9]+)(-(.*))?$', $rawresponse->answer, $answerregs)) {
- error("The answer value '$rawresponse->answer' for the response with "
- ."id=$rawresponse->id to the random question "
- ."$rawresponse->question is malformated."
- ." - No response can be extracted!");
- }
- $randomquestionid = $answerregs[2];
-
- if ($randomquestion = get_record('quiz_questions',
- 'id', $randomquestionid)) {
-
- if ($answerregs[1] && $answerregs[3]) {
- // The raw response is formatted according to
- // Moodle version 1.5 or later
- $randomresponse = $rawresponse;
- $randomresponse->question = $randomquestionid;
- $randomresponse->answer = $answerregs[4];
-
- } else if ($randomresponse = get_record
- ('quiz_responses', 'question', $rawresponse->answer,
- 'attempt', $rawresponse->attempt)) {
- // The response was stored by an older version of Moodle
- // :-)
-
- } else {
- notify("Error: Cannot find response to random question $randomquestionid");
- unset($randomresponse);
- }
-
- if (isset($randomresponse)) {
- /// The prefered case:
- /// There is a random question and a response field, from
- /// which the response array can be extracted:
-
-
- } else {
-
- /// Instead: workaround by creating a new response:
- $response = $QUIZ_QTYPES[$randomquestion->qtype]
- ->create_response($randomquestion,
- quiz_qtype_nameprefix($randomquestion, $nameprefix),
- "$rawresponse->question,$randomquestionid");
- // (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] = $randomquestionid;
- //return $response;
- return '';
- } 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();
- }
- }
}
//// END OF CLASS ////
diff --git a/mod/quiz/questiontypes/rqp/questiontype.php b/mod/quiz/questiontypes/rqp/questiontype.php
index 6c0371cd81a..0bb459aec13 100644
--- a/mod/quiz/questiontypes/rqp/questiontype.php
+++ b/mod/quiz/questiontypes/rqp/questiontype.php
@@ -205,8 +205,17 @@ class quiz_rqp_qtype extends quiz_default_questiontype {
$options->persistent_data = $state->options->persistent_data;
$options->template_vars =
quiz_rqp_implode($state->options->template_vars);
- if (!insert_record('quiz_rqp_states', $options)) {
- return false;
+ if ($state->update) {
+ if (!$options->id = get_field('quiz_rqp_states', 'id', 'stateid', $state->id)) {
+ return false;
+ }
+ if (!update_record('quiz_rqp_states', $options)) {
+ return false;
+ }
+ } else {
+ if (!insert_record('quiz_rqp_states', $options)) {
+ return false;
+ }
}
return true;
}
diff --git a/mod/quiz/questiontypes/shortanswer/questiontype.php b/mod/quiz/questiontypes/shortanswer/questiontype.php
index 6eca338af38..7a4fd3b39ed 100644
--- a/mod/quiz/questiontypes/shortanswer/questiontype.php
+++ b/mod/quiz/questiontypes/shortanswer/questiontype.php
@@ -104,7 +104,7 @@ class quiz_shortanswer_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
- $readonly = empty($options->readonly) ? '' : 'disabled="disabled"';
+ $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$nameprefix = $question->name_prefix;
/// Print question text and media
diff --git a/mod/quiz/questiontypes/truefalse/questiontype.php b/mod/quiz/questiontypes/truefalse/questiontype.php
index c52a3f44bff..f6add002d44 100644
--- a/mod/quiz/questiontypes/truefalse/questiontype.php
+++ b/mod/quiz/questiontypes/truefalse/questiontype.php
@@ -120,7 +120,7 @@ class quiz_truefalse_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state);
- $readonly = $options->readonly ? ' disabled="disabled"' : '';
+ $readonly = $options->readonly ? ' readonly="readonly"' : '';
// Print question formulation
echo format_text($question->questiontext,
diff --git a/mod/quiz/report/regrade/report.php b/mod/quiz/report/regrade/report.php
index 0bb803d7aa3..ca9350de52d 100644
--- a/mod/quiz/report/regrade/report.php
+++ b/mod/quiz/report/regrade/report.php
@@ -12,10 +12,64 @@ class quiz_report extends quiz_default_report {
/// Print header
$this->print_header_and_tabs($cm, $course, $quiz, $reportmode="regrade");
- notify('Not yet implemented');
+ /// Fetch all attempts
+ if (!$attempts = get_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = 0")) {
+ print_heading(get_string('noattempts', 'quiz'));
+ return true;
+ }
+
+ /// Fetch all questions
+ $sql = "SELECT q.*, i.grade AS maxgrade FROM {$CFG->prefix}quiz_questions q,
+ {$CFG->prefix}quiz_question_instances i
+ WHERE i.quiz = $quiz->id
+ AND i.question = q.id";
+
+ if (! $questions = get_records_sql($sql)) {
+ error("Failed to get questions for regrading!");
+ }
+ quiz_get_question_options($questions);
+
+ /// Print heading
+ print_heading(get_string('regradingquiz', 'quiz', $quiz));
+ echo '