Merged from stable

This commit is contained in:
gustav_delius 2005-06-05 20:51:15 +00:00
parent cb998489a2
commit fc44ee0de8
17 changed files with 307 additions and 414 deletions

View file

@ -216,13 +216,13 @@
// list of questions needed by page // list of questions needed by page
$pagelist = quiz_questions_on_page($attempt->layout, $page); $pagelist = quiz_questions_on_page($attempt->layout, $page);
// add all questions that are on the submitted form
if ($newattempt) { if ($newattempt) {
$questionlist = $attempt->layout; $questionlist = quiz_questions_in_quiz($attempt->layout);
} else { } else {
$questionlist = $pagelist; $questionlist = $pagelist;
} }
// add all questions that are on the submitted form
if ($questionids) { if ($questionids) {
$questionlist .= ','.$questionids; $questionlist .= ','.$questionids;
} }
@ -253,6 +253,13 @@
error('Could not restore question sessions'); 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 ///////////////////////////////////////////////// /// Process form data /////////////////////////////////////////////////
@ -370,13 +377,16 @@
/// Print the attempt number or preview heading /// Print the attempt number or preview heading
if ($isteacher) { if ($isteacher) {
print_heading(get_string('previewquiz', 'quiz')); print_heading(get_string('previewquiz', 'quiz', format_string($quiz->name)));
unset($buttonoptions); unset($buttonoptions);
$buttonoptions['q'] = $quiz->id; $buttonoptions['q'] = $quiz->id;
$buttonoptions['forcenew'] = true; $buttonoptions['forcenew'] = true;
echo '<center>'; echo '<center>';
print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz')); print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz'));
echo '</center>'; echo '</center>';
if ($quiz->popup) {
notify(get_string('popupnotice', 'quiz'));
}
} else { } else {
print_heading($strattemptnum); print_heading($strattemptnum);
} }
@ -434,14 +444,6 @@
quiz_save_question_session($questions[$i], $states[$i]); quiz_save_question_session($questions[$i], $states[$i]);
$number += $questions[$i]->length; $number += $questions[$i]->length;
} }
if ($newattempt) {
$questionlist = explode(',', $attempt->layout);
foreach ($questionlist as $i) {
if ($i != 0) {
quiz_save_question_session($questions[$i], $states[$i]);
}
}
}
/// Print the submit buttons /// Print the submit buttons

View file

@ -932,7 +932,7 @@
fwrite ($bf,full_tag("ID",8,false,$newest_state->id)); fwrite ($bf,full_tag("ID",8,false,$newest_state->id));
fwrite ($bf,full_tag("QUESTIONID",8,false,$newest_state->questionid)); fwrite ($bf,full_tag("QUESTIONID",8,false,$newest_state->questionid));
fwrite ($bf,full_tag("NEWEST",8,false,$newest_state->newest)); fwrite ($bf,full_tag("NEWEST",8,false,$newest_state->newest));
fwrite ($bf,full_tag("NEWESTGRADED",8,false,$newest_state->newestgraded)); fwrite ($bf,full_tag("NEWGRADED",8,false,$newest_state->newgraded));
fwrite ($bf,full_tag("SUMPENALTY",8,false,$newest_state->sumpenalty)); fwrite ($bf,full_tag("SUMPENALTY",8,false,$newest_state->sumpenalty));
//End newest_state //End newest_state
$status =fwrite ($bf,end_tag("NEWEST_STATE",7,true)); $status =fwrite ($bf,end_tag("NEWEST_STATE",7,true));

View file

@ -3,11 +3,11 @@
-- http://www.phpmyadmin.net -- http://www.phpmyadmin.net
-- --
-- Host: localhost -- Host: localhost
-- Generation Time: May 25, 2005 at 06:50 AM -- Generation Time: Jun 05, 2005 at 04:32 PM
-- Server version: 4.0.15 -- Server version: 4.0.15
-- PHP Version: 4.3.3 -- PHP Version: 4.3.3
-- --
-- Database: `upgrading` -- Database: `moodle15`
-- --
-- -------------------------------------------------------- -- --------------------------------------------------------
@ -399,7 +399,7 @@ CREATE TABLE prefix_quiz_rqp (
-- -------------------------------------------------------- -- --------------------------------------------------------
-- --
-- Table structure for table `prefix_quiz_rqp_states` -- Table structure for table `prefix_quiz_rqp_servers`
-- --
CREATE TABLE prefix_quiz_rqp_servers ( CREATE TABLE prefix_quiz_rqp_servers (
@ -414,7 +414,7 @@ CREATE TABLE prefix_quiz_rqp_servers (
-- -------------------------------------------------------- -- --------------------------------------------------------
-- --
-- Table structure for table `mdl_quiz_rqp_states` -- Table structure for table `prefix_quiz_rqp_states`
-- --
CREATE TABLE prefix_quiz_rqp_states ( CREATE TABLE prefix_quiz_rqp_states (
@ -429,15 +429,12 @@ CREATE TABLE prefix_quiz_rqp_states (
-- -------------------------------------------------------- -- --------------------------------------------------------
-- --
-- Table structure for table `prefix_quiz_rqp_type` -- Table structure for table `prefix_quiz_rqp_types`
-- --
CREATE TABLE prefix_quiz_rqp_types ( CREATE TABLE prefix_quiz_rqp_types (
id int(10) unsigned NOT NULL auto_increment, id int(10) unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL default '', name varchar(255) NOT NULL default '',
rendering_server varchar(255) NOT NULL default '',
cloning_server varchar(255) NOT NULL default '',
flags tinyint(3) NOT NULL default '0',
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY name (name) UNIQUE KEY name (name)
) TYPE=MyISAM COMMENT='RQP question types and the servers to be used to process the'; ) TYPE=MyISAM COMMENT='RQP question types and the servers to be used to process the';

View file

@ -408,14 +408,13 @@ if (self.name == 'editquestion') {
if (! $cm = get_coursemodule_from_instance("quiz", $modform->instance, $course->id)) { if (! $cm = get_coursemodule_from_instance("quiz", $modform->instance, $course->id)) {
error("Course Module ID was incorrect"); error("Course Module ID was incorrect");
} }
notify("$strattemptsexist<br /><a href=\"report.php?mode=overview&amp;id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>");
echo "<center>\n"; echo "<center>\n";
echo "$strattemptsexist<br /><a href=\"report.php?mode=overview&amp;id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>";
echo "<form target=\"_parent\" method=\"get\" action=\"$CFG->wwwroot/mod/quiz/edit.php\">\n"; echo "<form target=\"_parent\" method=\"get\" action=\"$CFG->wwwroot/mod/quiz/edit.php\">\n";
echo " <input type=\"hidden\" name=\"courseid\" value=\"$course->id\" />\n"; echo " <input type=\"hidden\" name=\"courseid\" value=\"$course->id\" />\n";
echo " <input type=\"submit\" value=\"".get_string("editcatquestions", "quiz")."\" />\n"; echo " <input type=\"submit\" value=\"".get_string("editcatquestions", "quiz")."\" />\n";
echo "</form>"; echo "</form>";
echo "</center>\n"; echo "</center><br/ >\n";
$sumgrades = quiz_print_question_list($modform, false, $SESSION->quiz_showbreaks); $sumgrades = quiz_print_question_list($modform, false, $SESSION->quiz_showbreaks);
if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $modform->instance)) { if (!set_field('quiz', 'sumgrades', $sumgrades, 'id', $modform->instance)) {

View file

@ -247,7 +247,7 @@ class quiz_default_questiontype {
// The default implementation attaches all answers for this question // The default implementation attaches all answers for this question
if (!$question->options->answers = get_records('quiz_answers', 'question', if (!$question->options->answers = get_records('quiz_answers', 'question',
$question->id)) { $question->id)) {
notify('Error: Missing question answers!'); //notify('Error: Missing question answers!');
return false; return false;
} }
return true; return true;
@ -1349,39 +1349,43 @@ function quiz_save_question_session(&$question, &$state) {
$state->answer = isset($state->responses['']) ? $state->responses[''] : ''; $state->answer = isset($state->responses['']) ? $state->responses[''] : '';
// Save the state // Save the state
unset($state->id); if (isset($state->update)) {
if (!$state->id = insert_record('quiz_states', $state)) { update_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');
}
} else { } else {
set_field('quiz_newest_states', 'newest', $state->id, 'attemptid', if (!$state->id = insert_record('quiz_states', $state)) {
$state->attempt, 'questionid', $question->id); unset($state->id);
} unset($state->answer);
if (quiz_state_is_graded($state)) { return false;
// this is also the most recent graded state }
if ($newest = get_record('quiz_newest_states', 'attemptid',
// this is the most recent state
if (!record_exists('quiz_newest_states', 'attemptid',
$state->attempt, 'questionid', $question->id)) { $state->attempt, 'questionid', $question->id)) {
$newest->newgraded = $state->id; $new->attemptid = $state->attempt;
$newest->sumpenalty = $state->sumpenalty; $new->questionid = $question->id;
update_record('quiz_newest_states', $newest); $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 // Save the question type specific state information and responses
if (!$QUIZ_QTYPES[$question->qtype]->save_session_and_responses( if (!$QUIZ_QTYPES[$question->qtype]->save_session_and_responses(
$question, $state)) { $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 * For a given question in an attempt we walk the complete history of states
* each user and recalculate the grades as we go along. * and recalculate the grades as we go along.
* *
* This is used when a question in an existing quiz is changed and old student * 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. * responses need to be marked with the new version of a question.
* *
* TODO: Finish documenting this * TODO: Finish documenting this
* @return boolean Indicates success/failure * @return boolean Indicates success/failure
* @param object $question A question object * @param object $question A question object
* @param array $quizlist An array of quiz ids, in which the question should * @param object $attempt The attempt, in which the question needs to be regraded.
* be regraded. If quizlist == 'all' all quizzes are affected * @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) { function quiz_regrade_question_in_attempt($question, $attempt, $quiz=false, $verbose=false) {
if (!$quiz && !($quiz = get_record('quiz', 'id', $attempt->quiz))) {
// Disable until tested $verbose && notify("Regrading of quiz #{$attempt->quiz} failed; " .
return; "Couldn't load quiz record from database!");
return false;
if (empty($quizlist)) {
return;
} }
if ($quizlist == 'all') { // assume that all quizzes are affected if ($states = get_records_select('quiz_states',
// fetch a list of all the quizzes using this question "attempt = '{$attempt->id}' AND question = '{$question->id}'", 'seq_number ASC')) {
if (! $instances = (array)get_records('quiz_question_instances', $states = array_values($states);
'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);
}
// Get all affected quizzes $attempt->sumgrades -= $states[count($states)-1]->grade;
$quizlist = implode(',', $quizlist);
if (! $quizzes = get_records_list('quiz', 'id', $quizlist)) {
error('Couldn\'t get quizzes for regrading!');
}
foreach ($quizzes as $quiz) { // Initialise the replaystate
// All the attempts that need to be changed $state = clone($states[0]);
if (! $attempts = get_records('quiz_attempts', 'quiz', $quiz->id)) { quiz_restore_state($question, $state);
continue; $state->sumpenalty = 0.0;
} $state->raw_grade = 0;
$attempts = array_values($attempts); $state->grade = 0;
if (! $instance = get_record('quiz_question_instances', $state->responses = array(''=>'');
'quiz', $quiz->id, 'question', $question->id)) { $state->event = QUIZ_EVENTOPEN;
error("Couldn't get question instance for regrading!"); $replaystate = clone($state);
} $replaystate->last_graded = $state;
$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);
$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 // Close the last state of a finished attempt
quiz_restore_state($question, $states[0]); if (((count($states) - 1) === $j) && ($attempt->timefinish > 0)) {
$replaystate = clone($states[0]); $action->event = QUIZ_EVENTCLOSE;
$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;
// Grade instead of closing, quiz_process_responses will then // Grade instead of closing, quiz_process_responses will then
// work out whether to close it // work out whether to close it
} else if (QUIZ_EVENTCLOSE == $states[$j]->event) { } else if (QUIZ_EVENTCLOSE == $states[$j]->event) {
$action->event = QUIZ_EVENTGRADE; $action->event = QUIZ_EVENTGRADE;
// By default take the event that was saved in the database // By default take the event that was saved in the database
} else { } else {
$action->event = $states[$j]->event; $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);
} }
// 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.'&amp;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. * @return boolean Indicates success or failure.
* @param object $quiz The quiz for which the best grade is to be calculated * @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 // Calculate the best grade
$bestgrade = quiz_calculate_best_grade($quiz, $attempts); $bestgrade = quiz_calculate_best_grade($quiz, $attempts);
$bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade); $bestgrade = (($bestgrade / $quiz->sumgrades) * $quiz->grade);
$bestgrade = round($bestgrade, $quiz->decimalpoints);
// Save the best grade in the database // Save the best grade in the database
if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', if ($grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid',
$userid)) { $userid)) {
$grade->grade = round($bestgrade, $quiz->decimalpoints); $grade->grade = $bestgrade;
$grade->timemodified = time(); $grade->timemodified = time();
if (!update_record('quiz_grades', $grade)) { if (!update_record('quiz_grades', $grade)) {
notify('Could not update best grade'); notify('Could not update best grade');
@ -2282,7 +2281,7 @@ function quiz_save_best_grade($quiz, $userid=null) {
} else { } else {
$grade->quiz = $quiz->id; $grade->quiz = $quiz->id;
$grade->userid = $userid; $grade->userid = $userid;
$grade->grade = round($bestgrade, $quiz->decimalpoints); $grade->grade = $bestgrade;
$grade->timemodified = time(); $grade->timemodified = time();
if (!insert_record('quiz_grades', $grade)) { if (!insert_record('quiz_grades', $grade)) {
notify('Could not insert new best grade'); notify('Could not insert new best grade');
@ -2292,9 +2291,14 @@ function quiz_save_best_grade($quiz, $userid=null) {
return true; 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) { 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) { 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) { function quiz_calculate_best_attempt($quiz, $attempts) {
/// Return the attempt with the best grade for a quiz
switch ($quiz->grademethod) { switch ($quiz->grademethod) {

View file

@ -256,7 +256,7 @@
set_field('quiz_newest_states', 'questionid', $question->id, 'attemptid', $attempt->id, 'questionid', $oldquestionid); 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 // 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 // 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 // have to recode the answer ids in the states
@ -272,11 +272,12 @@
} }
if (empty($question->errors) && $QUIZ_QTYPES[$qtype]->finished_edit_wizard($form)) { if (empty($question->errors) && $QUIZ_QTYPES[$qtype]->finished_edit_wizard($form)) {
// DISABLED AUTOMATIC REGRADING
// Automagically regrade all attempts (and states) in the affected quizzes // Automagically regrade all attempts (and states) in the affected quizzes
if (!empty($replaceinquiz)) { //if (!empty($replaceinquiz)) {
$QUIZ_QTYPES[$question->qtype]->get_question_options($question); // $QUIZ_QTYPES[$question->qtype]->get_question_options($question);
quiz_regrade_question_in_quizzes($question, $replaceinquiz); // quiz_regrade_question_in_quizzes($question, $replaceinquiz);
} //}
redirect("edit.php"); redirect("edit.php");
} }
} }

View file

@ -368,7 +368,8 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
} }
// Get answers // 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'); $stranswers = get_string('answer', 'quiz');
$strmin = get_string('min', 'quiz'); $strmin = get_string('min', 'quiz');
$strmax = get_string('max', 'quiz'); $strmax = get_string('max', 'quiz');
@ -378,26 +379,28 @@ class quiz_calculated_qtype extends quiz_dataset_dependent_questiontype {
$state->responses = array(); $state->responses = array();
$state->options = new stdClass; $state->options = new stdClass;
$virtualqtype = $this->get_virtual_qtype(); $virtualqtype = $this->get_virtual_qtype();
foreach ($answers as $answer) { if ($answers) {
$calculated = quiz_qtype_calculated_calculate_answer( foreach ($answers as $answer) {
$answer->answer, $data, $answer->tolerance, $calculated = quiz_qtype_calculated_calculate_answer(
$answer->tolerancetype, $answer->correctanswerlength, $answer->answer, $data, $answer->tolerance,
$answer->correctanswerformat, $unit); $answer->tolerancetype, $answer->correctanswerlength,
$state->responses[''] = $calculated->answer; $answer->correctanswerformat, $unit);
$virtualqtype->get_tolerance_interval($question, $state); $state->responses[''] = $calculated->answer;
$calculated->min = $state->options->min; $virtualqtype->get_tolerance_interval($question, $state);
$calculated->max = $state->options->max; $calculated->min = $state->options->min;
if ($calculated->min === '') { $calculated->max = $state->options->max;
// This should mean that something is wrong if ($calculated->min === '') {
$errors .= " -$calculated->answer"; // This should mean that something is wrong
$stranswers .= $delimiter; $errors .= " -$calculated->answer";
} else { $stranswers .= $delimiter;
$stranswers .= $delimiter.$calculated->answer; } 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<br/>$strmin<br/>$strmax<br/>$errors"; return "$stranswers<br/>$strmin<br/>$strmax<br/>$errors";
} }

View file

@ -239,7 +239,7 @@ class quiz_multichoice_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers; $answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state); $correctanswers = $this->get_correct_responses($question, $state);
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$formatoptions = new stdClass; $formatoptions = new stdClass;
$formatoptions->para = false; $formatoptions->para = false;

View file

@ -246,7 +246,7 @@ class quiz_numerical_qtype extends quiz_shortanswer_qtype {
$answers = &$question->options->answers; $answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state); $correctanswers = $this->get_correct_responses($question, $state);
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$nameprefix = $question->name_prefix; $nameprefix = $question->name_prefix;
/// Print question text and media /// Print question text and media

View file

@ -7,13 +7,7 @@
/// QUESTION TYPE CLASS ////////////////// /// QUESTION TYPE CLASS //////////////////
class quiz_random_qtype extends quiz_default_questiontype { class quiz_random_qtype extends quiz_default_questiontype {
var $possiblerandomqtypes = array(SHORTANSWER, var $excludedtypes = array(RANDOM, RANDOMSAMATCH);
NUMERICAL,
MULTICHOICE,
MATCH,
// RANDOMSAMATCH,// Can cause unexpected outcomes
TRUEFALSE,
MULTIANSWER);
// Carries questions available as randoms sorted by category // Carries questions available as randoms sorted by category
// This array is used when needed only // 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" // Need to fetch random questions from category $question->category"
// (Note: $this refers to the questiontype, not the question.) // (Note: $this refers to the questiontype, not the question.)
global $CFG; global $CFG;
$possiblerandomqtypes = "'" $excludedtypes = implode(',', $this->excludedtypes);
. implode("','", $this->possiblerandomqtypes) . "'";
if ($question->questiontext == "1") { if ($question->questiontext == "1") {
// recurse into subcategories // recurse into subcategories
$categorylist = quiz_categorylist($question->category); $categorylist = quiz_categorylist($question->category);
@ -65,7 +58,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
WHERE category IN ($categorylist) WHERE category IN ($categorylist)
AND parent = '0' AND parent = '0'
AND id NOT IN ($quiz->questionsinuse) AND id NOT IN ($quiz->questionsinuse)
AND qtype IN ($possiblerandomqtypes)"); AND qtype NOT IN ($excludedtypes)");
$this->catrandoms[$question->category] = $this->catrandoms[$question->category] =
draw_rand_array($this->catrandoms[$question->category], draw_rand_array($this->catrandoms[$question->category],
count($this->catrandoms[$question->category])); // from bug 1889 count($this->catrandoms[$question->category])); // from bug 1889
@ -80,13 +73,6 @@ class quiz_random_qtype extends quiz_default_questiontype {
global $QUIZ_QTYPES; global $QUIZ_QTYPES;
$QUIZ_QTYPES[$wrappedquestion->qtype] $QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion); ->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] $QUIZ_QTYPES[$wrappedquestion->qtype]
->create_session_and_responses($wrappedquestion, ->create_session_and_responses($wrappedquestion,
$state, $quiz, $attempt); $state, $quiz, $attempt);
@ -97,38 +83,53 @@ class quiz_random_qtype extends quiz_default_questiontype {
return true; return true;
} }
} }
notify(get_string('toomanyrandom', 'quiz', $question->category)); $question->questiontext = '<span class="notifyproblem">'.
return false; get_string('toomanyrandom', 'quiz', $question->category). '</span>';
$question->qtype = DESCRIPTION;
$state->responses = array('' => '');
return true;
} }
function restore_session_and_responses(&$question, &$state) { 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; global $QUIZ_QTYPES;
if(!$randomstate = get_record('quiz_states', 'question', if (!ereg('^random([0-9]+)-(.*)$', $state->responses[''], $answerregs)) {
$question->id, 'attempt', $state->attempt)) { // this must be an old-style state which stores only the id for the wrapped question
return false; 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] if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->get_question_options($wrappedquestion)) { ->get_question_options($wrappedquestion)) {
return false; 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] if (!$QUIZ_QTYPES[$wrappedquestion->qtype]
->restore_session_and_responses($wrappedquestion, $state)) { ->restore_session_and_responses($wrappedquestion, $state)) {
return false; return false;
@ -136,21 +137,12 @@ class quiz_random_qtype extends quiz_default_questiontype {
$wrappedquestion->name_prefix = $question->name_prefix; $wrappedquestion->name_prefix = $question->name_prefix;
$wrappedquestion->maxgrade = $question->maxgrade; $wrappedquestion->maxgrade = $question->maxgrade;
$state->options->question = &$wrappedquestion; $state->options->question = &$wrappedquestion;
$state->options->state = &$randomstate;
return true; return true;
} }
function save_session_and_responses(&$question, &$state) { function save_session_and_responses(&$question, &$state) {
global $QUIZ_QTYPES; global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question; $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. // Trick the wrapped question into pretending to be the random one.
$realqid = $wrappedquestion->id; $realqid = $wrappedquestion->id;
@ -158,6 +150,21 @@ class quiz_random_qtype extends quiz_default_questiontype {
$QUIZ_QTYPES[$wrappedquestion->qtype] $QUIZ_QTYPES[$wrappedquestion->qtype]
->save_session_and_responses($wrappedquestion, $state); ->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 // Restore the real id
$wrappedquestion->id = $realqid; $wrappedquestion->id = $realqid;
return true; return true;
@ -193,34 +200,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
$QUIZ_QTYPES[$wrappedquestion->qtype] $QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question($wrappedquestion, $state, $number, $quiz, $options); ->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) { function grade_responses(&$question, &$state, $quiz) {
global $QUIZ_QTYPES; global $QUIZ_QTYPES;
$wrappedquestion = &$state->options->question; $wrappedquestion = &$state->options->question;
@ -255,173 +235,7 @@ class quiz_random_qtype extends quiz_default_questiontype {
return $QUIZ_QTYPES[$wrappedquestion->qtype] return $QUIZ_QTYPES[$wrappedquestion->qtype]
->print_question_form_end($wrappedquestion, $state, $quizid); ->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 '<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])) {
$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 //// //// END OF CLASS ////

View file

@ -205,8 +205,17 @@ class quiz_rqp_qtype extends quiz_default_questiontype {
$options->persistent_data = $state->options->persistent_data; $options->persistent_data = $state->options->persistent_data;
$options->template_vars = $options->template_vars =
quiz_rqp_implode($state->options->template_vars); quiz_rqp_implode($state->options->template_vars);
if (!insert_record('quiz_rqp_states', $options)) { if ($state->update) {
return false; 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; return true;
} }

View file

@ -104,7 +104,7 @@ class quiz_shortanswer_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers; $answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state); $correctanswers = $this->get_correct_responses($question, $state);
$readonly = empty($options->readonly) ? '' : 'disabled="disabled"'; $readonly = empty($options->readonly) ? '' : 'readonly="readonly"';
$nameprefix = $question->name_prefix; $nameprefix = $question->name_prefix;
/// Print question text and media /// Print question text and media

View file

@ -120,7 +120,7 @@ class quiz_truefalse_qtype extends quiz_default_questiontype {
$answers = &$question->options->answers; $answers = &$question->options->answers;
$correctanswers = $this->get_correct_responses($question, $state); $correctanswers = $this->get_correct_responses($question, $state);
$readonly = $options->readonly ? ' disabled="disabled"' : ''; $readonly = $options->readonly ? ' readonly="readonly"' : '';
// Print question formulation // Print question formulation
echo format_text($question->questiontext, echo format_text($question->questiontext,

View file

@ -12,10 +12,64 @@ class quiz_report extends quiz_default_report {
/// Print header /// Print header
$this->print_header_and_tabs($cm, $course, $quiz, $reportmode="regrade"); $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 '<center>';
print_string('regradedisplayexplanation', 'quiz');
echo '<center>';
/// Loop through all questions and all attempts and regrade while printing progress info
foreach ($questions as $question) {
echo '<b>'.get_string('regradingquestion', 'quiz', $question->name).'</b> '.get_string('attempts', 'quiz').": \n";
foreach ($attempts as $attempt) {
quiz_regrade_question_in_attempt($question, $attempt, $quiz, true);
}
echo '<br/ >';
// the following makes sure that the output is sent immediately.
flush();ob_flush();
}
/// Loop through all questions and recalculate $attempt->sumgrade
$attemptschanged = 0;
foreach ($attempts as $attempt) {
$sumgrades = 0;
$questionids = explode(',', quiz_questions_in_quiz($attempt->layout));
foreach($questionids as $questionid) {
$lastgradedid = get_field('quiz_newest_states', 'newgraded', 'attemptid', $attempt->id, 'questionid', $questionid);
$sumgrades += get_field('quiz_states', 'grade', 'id', $lastgradedid);
}
if ($attempt->sumgrades != $sumgrades) {
$attemptschanged++;
set_field('quiz_attempts', 'sumgrades', $sumgrades, 'id', $attempt->id);
}
}
/// Update the overall quiz grades
if ($grades = get_records('quiz_grades', 'quiz', $quiz->id)) {
foreach($grades as $grade) {
quiz_save_best_grade($quiz, $grade->userid);
}
}
return true; return true;
} }
} }
?> ?>

View file

@ -1420,6 +1420,9 @@
//This function restores the quiz_attempts //This function restores the quiz_attempts
function quiz_attempts_restore_mods($quiz_id,$info,$restore) { function quiz_attempts_restore_mods($quiz_id,$info,$restore) {
notify("Restoring quiz without user attempts. Restoring of user attempts will be implemented in Moodle 1.5.1");
return true;
global $CFG; global $CFG;
$status = true; $status = true;
@ -1761,7 +1764,7 @@
return $status; return $status;
} }
//This function restores the quiz_states //This function restores the quiz_rqp_states
function quiz_rqp_states_restore_mods($state_id,$info,$restore) { function quiz_rqp_states_restore_mods($state_id,$info,$restore) {
global $CFG; global $CFG;

View file

@ -18,8 +18,7 @@
$stateid = optional_param('state', 0, PARAM_INT); // state id $stateid = optional_param('state', 0, PARAM_INT); // state id
$attemptid = optional_param('attempt', 0, PARAM_INT); // attempt id $attemptid = optional_param('attempt', 0, PARAM_INT); // attempt id
$questionid = optional_param('question', 0, PARAM_INT); // attempt id $questionid = optional_param('question', 0, PARAM_INT); // attempt id
$number = optional_param('number', 0, PARAM_INT); // question number
$number = required_param('number', PARAM_INT); // question number
if ($stateid) { if ($stateid) {
if (! $state = get_record('quiz_states', 'id', $stateid)) { if (! $state = get_record('quiz_states', 'id', $stateid)) {

View file

@ -113,13 +113,14 @@
// This is all the teacher will get // This is all the teacher will get
if ($isteacher) { if ($isteacher) {
$attemptcount = count_records('quiz_attempts', 'quiz', $quiz->id, 'preview', 0); if ($attemptcount = count_records('quiz_attempts', 'quiz', $quiz->id, 'preview', 0)) {
$strviewallanswers = get_string("viewallanswers", "quiz", $attemptcount); $strviewallanswers = get_string("viewallanswers", "quiz", $attemptcount);
$usercount = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)'); $usercount = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)');
$strusers = $course->students; $strusers = $course->students;
notify("<a href=\"report.php?mode=overview&amp;id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>"); notify("<a href=\"report.php?mode=overview&amp;id=$cm->id\">$strviewallanswers ($usercount $strusers)</a>");
}
print_footer($course); print_footer($course);
exit; exit;
} }