Index: mod/quiz/attempt.php =================================================================== RCS file: /cvsroot/moodle/moodle/mod/quiz/attempt.php,v retrieving revision 1.131.2.17 diff -u -r1.131.2.17 attempt.php --- mod/quiz/attempt.php 30 Sep 2009 10:58:03 -0000 1.131.2.17 +++ mod/quiz/attempt.php 10 Nov 2009 17:01:10 -0000 @@ -82,9 +82,11 @@ /// We intentionally do not check open and close times here. Instead we do it lower down. /// This is to deal with what happens when someone submits close to the exact moment when the quiz closes. -/// Check number of attempts - $numberofpreviousattempts = count_records_select('quiz_attempts', "quiz = '{$quiz->id}' AND " . - "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1"); +/// Get previous attempts of this user +/// And also we will need 'uniqueid's of all previous attempts to avoid using one question twice + $previousattempts = array_keys(get_records_select('quiz_attempts', "quiz = '{$quiz->id}' AND " . + "userid = '{$USER->id}' AND timefinish > 0 AND preview != 1", '','uniqueid')); + $numberofpreviousattempts = count($previousattempts); if (!empty($quiz->attempts) and $numberofpreviousattempts >= $quiz->attempts) { print_error('nomoreattempts', 'quiz', "view.php?id={$cm->id}"); } @@ -264,6 +266,10 @@ } } + // MDL-6340: random question should know old attempts of this user to avoid using one question twice + $previousattemptslist = implode(',', $previousattempts); + $quiz->previousattempts = $previousattemptslist; + // Restore the question sessions to their most recent states // creating new sessions where required if (!$states = get_question_states($questions, $quiz, $attempt, $lastattemptid)) { Index: question/type/random/questiontype.php =================================================================== RCS file: /cvsroot/moodle/moodle/question/type/random/questiontype.php,v retrieving revision 1.12.2.9 diff -u -r1.12.2.9 questiontype.php --- question/type/random/questiontype.php 17 Feb 2009 06:14:48 -0000 1.12.2.9 +++ question/type/random/questiontype.php 10 Nov 2009 17:24:44 -0000 @@ -134,24 +134,57 @@ * @param integer $categoryid the id of a question category. * @param boolean whether to include questions from subcategories. * @param string $questionsinuse comma-separated list of question ids to exclude from consideration. - * @return array of question records. + * @param string $avoidquestionsfromattempts comma-separated list of attempt ids of this user. + * @return array of question ids with less used questions placed last. */ - function get_usable_questions_from_category($categoryid, $subcategories, $questionsinuse) { + function get_usable_questions_from_category($categoryid, $subcategories, $questionsinuse, $avoidquestionsfromattempts) { $this->init_qtype_lists(); if ($subcategories) { $categorylist = question_categorylist($categoryid); } else { $categorylist = $categoryid; } - if (!$catrandoms = get_records_select('question', - "category IN ($categorylist) - AND parent = 0 - AND hidden = 0 - AND id NOT IN ($questionsinuse) - AND qtype NOT IN ($this->excludedqtypes)", '', 'id')) { - $catrandoms = array(); + + if (!isset($avoidquestionsfromattempts)) { + /// If this is first attempt for this user we simply get all question id's + /// that can be used in this random + if (!$catrandoms = get_records_select('question', + "category IN ($categorylist) + AND parent = 0 + AND hidden = 0 + AND id NOT IN ($questionsinuse) + AND qtype NOT IN ($this->excludedqtypes)", '', 'id')) { + $catrandoms = array(); + } + return swapshuffle(array_keys($catrandoms)); + } else { + /// Get list of usable questions with question id's as keys and + /// number of previous uses by this user as value + $previoususes = get_records_sql_menu("SELECT q.id, COUNT(qst.id) AS numprevioususes + FROM mdl_question q + LEFT JOIN mdl_question_states qst ON qst.answer LIKE CONCAT('random', q.id, '-%') + WHERE q.category IN ($categorylist) + AND q.parent = 0 + AND q.hidden = 0 + AND q.id NOT IN ($questionsinuse) + AND qtype NOT IN ($this->excludedqtypes) + AND ((qst.attempt IS NULL) OR (qst.attempt IN ($avoidquestionsfromattempts))) + AND ((qst.seq_number IS NULL) OR (qst.seq_number = 0)) + GROUP BY q.id + ORDER BY numprevioususes ASC"); + + /// Since no question can be used twice in one attempt and we are trying hard to use + /// less used questions first there should be at most two subsequent numbers of previous uses. + $minusedcount = current($previoususes); + $minused = array_keys($previoususes, $minusedcount); + $maxused = array_keys($previoususes, $minusedcount + 1); + + /// Now we should reshuffle questions with each number of uses separately and merge them + /// in array of question ids placing less used questions last. + $catrandoms = array_merge(swapshuffle($maxused), swapshuffle($minused)); + + return $catrandoms; } - return $catrandoms; } function create_session_and_responses(&$question, &$state, $cmoptions, $attempt) { @@ -169,16 +202,16 @@ if (!isset($this->catrandoms[$question->category][$question->questiontext])) { $catrandoms = $this->get_usable_questions_from_category($question->category, - $question->questiontext == "1", $cmoptions->questionsinuse); - $this->catrandoms[$question->category][$question->questiontext] = swapshuffle_assoc($catrandoms); + $question->questiontext == '1', $cmoptions->questionsinuse, $cmoptions->previousattempts); + $this->catrandoms[$question->category][$question->questiontext] = $catrandoms; } while ($wrappedquestion = array_pop($this->catrandoms[$question->category][$question->questiontext])) { - if (!ereg("(^|,)$wrappedquestion->id(,|$)", $cmoptions->questionsinuse)) { + if (!strpos(','.$cmoptions->questionsinuse.',', ','.$wrappedquestion.',')) { /// $randomquestion is not in use and will therefore be used /// as the randomquestion here... - $wrappedquestion = get_record('question', 'id', $wrappedquestion->id); + $wrappedquestion = get_record('question', 'id', $wrappedquestion); $QTYPES[$wrappedquestion->qtype] ->get_question_options($wrappedquestion); $QTYPES[$wrappedquestion->qtype]