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 '
'; print_single_button($CFG->wwwroot.'/mod/quiz/attempt.php', $buttonoptions, get_string('startagain', 'quiz')); echo '
'; + if ($quiz->popup) { + notify(get_string('popupnotice', 'quiz')); + } } else { print_heading($strattemptnum); } @@ -434,14 +444,6 @@ quiz_save_question_session($questions[$i], $states[$i]); $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 diff --git a/mod/quiz/backuplib.php b/mod/quiz/backuplib.php index a305be7678d..ef943090d0e 100644 --- a/mod/quiz/backuplib.php +++ b/mod/quiz/backuplib.php @@ -932,7 +932,7 @@ fwrite ($bf,full_tag("ID",8,false,$newest_state->id)); fwrite ($bf,full_tag("QUESTIONID",8,false,$newest_state->questionid)); 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)); //End newest_state $status =fwrite ($bf,end_tag("NEWEST_STATE",7,true)); diff --git a/mod/quiz/db/mysql.sql b/mod/quiz/db/mysql.sql index f10e91de1b3..8ff6e416bac 100644 --- a/mod/quiz/db/mysql.sql +++ b/mod/quiz/db/mysql.sql @@ -3,11 +3,11 @@ -- http://www.phpmyadmin.net -- -- 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 -- 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 ( @@ -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 ( @@ -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 ( id int(10) unsigned NOT NULL auto_increment, 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), UNIQUE KEY name (name) ) TYPE=MyISAM COMMENT='RQP question types and the servers to be used to process the'; diff --git a/mod/quiz/edit.php b/mod/quiz/edit.php index 5dc8463985c..61c60204667 100644 --- a/mod/quiz/edit.php +++ b/mod/quiz/edit.php @@ -408,14 +408,13 @@ if (self.name == 'editquestion') { if (! $cm = get_coursemodule_from_instance("quiz", $modform->instance, $course->id)) { error("Course Module ID was incorrect"); } - notify("$strattemptsexist
id\">$strviewallanswers ($usercount $strusers)"); - echo "
\n"; + echo "$strattemptsexist
id\">$strviewallanswers ($usercount $strusers)"; echo "
wwwroot/mod/quiz/edit.php\">\n"; echo " id\" />\n"; echo " \n"; echo "
"; - 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 '
'; + print_string('regradedisplayexplanation', 'quiz'); + echo '
'; + + /// Loop through all questions and all attempts and regrade while printing progress info + foreach ($questions as $question) { + echo ''.get_string('regradingquestion', 'quiz', $question->name).' '.get_string('attempts', 'quiz').": \n"; + foreach ($attempts as $attempt) { + quiz_regrade_question_in_attempt($question, $attempt, $quiz, true); + } + echo '
'; + // 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; } } -?> +?> \ No newline at end of file diff --git a/mod/quiz/restorelib.php b/mod/quiz/restorelib.php index fa3fa6297e2..9ea3c04aa17 100644 --- a/mod/quiz/restorelib.php +++ b/mod/quiz/restorelib.php @@ -1420,6 +1420,9 @@ //This function restores the quiz_attempts 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; $status = true; @@ -1761,7 +1764,7 @@ 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) { global $CFG; diff --git a/mod/quiz/reviewquestion.php b/mod/quiz/reviewquestion.php index 4484582b61f..584d34af904 100644 --- a/mod/quiz/reviewquestion.php +++ b/mod/quiz/reviewquestion.php @@ -18,8 +18,7 @@ $stateid = optional_param('state', 0, PARAM_INT); // state id $attemptid = optional_param('attempt', 0, PARAM_INT); // attempt id $questionid = optional_param('question', 0, PARAM_INT); // attempt id - - $number = required_param('number', PARAM_INT); // question number + $number = optional_param('number', 0, PARAM_INT); // question number if ($stateid) { if (! $state = get_record('quiz_states', 'id', $stateid)) { diff --git a/mod/quiz/view.php b/mod/quiz/view.php index be4eb0a6075..f796dd056b3 100644 --- a/mod/quiz/view.php +++ b/mod/quiz/view.php @@ -113,13 +113,14 @@ // This is all the teacher will get 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); - $usercount = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)'); - $strusers = $course->students; - - notify("id\">$strviewallanswers ($usercount $strusers)"); + $strviewallanswers = get_string("viewallanswers", "quiz", $attemptcount); + $usercount = count_records_select('quiz_attempts', "quiz = '$quiz->id' AND preview = '0'", 'COUNT(DISTINCT userid)'); + $strusers = $course->students; + + notify("id\">$strviewallanswers ($usercount $strusers)"); + } print_footer($course); exit; }