diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index 381e0ddb825..7ec636bd3e2 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -14,56 +14,12 @@ require_once($CFG->libdir.'/pagelib.php'); /// CONSTANTS /////////////////////////////////////////////////////////////////// - -/**#@+ -* Option flags for $quiz->optionflags -*/ -/** -* Whether the quiz is to be run in adaptive mode -*/ -define('QUIZ_ADAPTIVE', 1); - -/** -* Determines if when checking whether an answer should -* be checked against all previous ones to see if it is duplicate -*/ -define('QUIZ_IGNORE_DUPRESP', 2); - -/**#@-*/ - /** * If start and end date for the quiz are more than this many seconds apart * they will be represented by two separate events in the calendar */ define("QUIZ_MAX_EVENT_LENGTH", "432000"); // 5 days maximum -/**#@+ -* The different review options are stored in the bits of $quiz->review -* These constants help to extract the options -*/ -/** -* The first 6 bits refer to the time immediately after the attempt -*/ -define('QUIZ_REVIEW_IMMEDIATELY', 64-1); -/** -* the next 6 bits refer to the time after the attempt but while the quiz is open -*/ -define('QUIZ_REVIEW_OPEN', 4096-64); -/** -* the final 6 bits refer to the time after the quiz closes -*/ -define('QUIZ_REVIEW_CLOSED', 262144-4096); - -// within each group of 6 bits we determine what should be shown -define('QUIZ_REVIEW_RESPONSES', 1+64+4096); // Show responses -define('QUIZ_REVIEW_SCORES', 2*4161); // Show scores -define('QUIZ_REVIEW_FEEDBACK', 4*4161); // Show feedback -define('QUIZ_REVIEW_ANSWERS', 8*4161); // Show correct answers -// Some handling of worked solutions is already in the code but not yet fully supported -// and not switched on in the user interface. -define('QUIZ_REVIEW_SOLUTIONS', 16*4161); // Show solutions -// the 6th bit is as yet unused -/**#@-*/ /// FUNCTIONS /////////////////////////////////////////////////////////////////// diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 5d099ae0105..ab495280c8a 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -72,8 +72,7 @@ function quiz_create_attempt($quiz, $attemptnumber) { $attempt->timestart = $timenow; $attempt->timefinish = 0; $attempt->timemodified = $timenow; - $attempt->uniqueid = $CFG->attemptuniqueid; - set_config('attemptuniqueid', $CFG->attemptuniqueid + 1); + $attempt->uniqueid = quiz_new_attempt_uniqueid(); return $attempt; } @@ -427,68 +426,6 @@ function quiz_parse_fieldname($name, $nameprefix='question') { } -/** -* Determine render options -*/ -function quiz_get_renderoptions($quiz, $state) { - // Show the question in readonly (review) mode if the quiz is in - // the closed state - $options->readonly = QUIZ_EVENTCLOSE === $state->event; - - // Show feedback once the question has been graded (if allowed by the quiz) - $options->feedback = ('' !== $state->grade) && ($quiz->review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY); - - // Show validation only after a validation event - $options->validation = QUIZ_EVENTVALIDATE === $state->event; - - // Show correct responses in readonly mode if the quiz allows it - $options->correct_responses = $options->readonly && ($quiz->review & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY); - - // Always show responses and scores - $options->responses = true; - $options->scores = true; - - return $options; -} - - -/** -* Determine review options -*/ -function quiz_get_reviewoptions($quiz, $attempt, $isteacher=false) { - $options->readonly = true; - if ($isteacher and !$attempt->preview) { - // The teacher should be shown everything except during preview when the teachers - // wants to see just what the students see - $options->responses = true; - $options->scores = true; - $options->feedback = true; - $options->correct_responses = true; - $options->solutions = false; - return $options; - } - if ((time() - $attempt->timefinish) < 120) { - $options->responses = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_RESPONSES) ? 1 : 0; - $options->scores = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_SCORES) ? 1 : 0; - $options->feedback = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; - $options->correct_responses = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; - $options->solutions = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; - } else if (time() < $quiz->timeclose) { - $options->responses = ($quiz->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_RESPONSES) ? 1 : 0; - $options->scores = ($quiz->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_SCORES) ? 1 : 0; - $options->feedback = ($quiz->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; - $options->correct_responses = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; - $options->solutions = ($quiz->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; - } else { - $options->responses = ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES) ? 1 : 0; - $options->scores = ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_SCORES) ? 1 : 0; - $options->feedback = ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; - $options->correct_responses = ($quiz->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; - $options->solutions = ($quiz->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; - } - return $options; -} - /** * Upgrade states for an attempt to Moodle 1.5 model * diff --git a/mod/quiz/questionlib.php b/mod/quiz/questionlib.php index c6b5b232c3c..fbc7810f6d0 100644 --- a/mod/quiz/questionlib.php +++ b/mod/quiz/questionlib.php @@ -17,6 +17,56 @@ /// CONSTANTS /////////////////////////////////////////////////////////////////// +/**#@+ +* Option flags for ->optionflags +* The options are read out via bitwise operation using these constants +*/ +/** +* Whether the questions to be run in adaptive mode. If this is not set then +* a question closes immediately after the first submission of responses. This +* is how Moodle worked before version 1.5 +*/ +define('QUIZ_ADAPTIVE', 1); + +/** When processing responses the code checks that the new responses at +* a question differ from those given on the previous submission. If +* furthermore $ignoredupresp (ignore duplicate responses) is set to true +* then the code goes through the whole history of attempts and checks if +* ANY of them are identical to the current response in which case the +* current response is ignored. +*/ +define('QUIZ_IGNORE_DUPRESP', 2); + +/**#@-*/ + +/**#@+ +* The different review options are stored in the bits of $quiz->review +* These constants help to extract the options +*/ +/** +* The first 6 bits refer to the time immediately after the attempt +*/ +define('QUIZ_REVIEW_IMMEDIATELY', 64-1); +/** +* the next 6 bits refer to the time after the attempt but while the quiz is open +*/ +define('QUIZ_REVIEW_OPEN', 4096-64); +/** +* the final 6 bits refer to the time after the quiz closes +*/ +define('QUIZ_REVIEW_CLOSED', 262144-4096); + +// within each group of 6 bits we determine what should be shown +define('QUIZ_REVIEW_RESPONSES', 1+64+4096); // Show responses +define('QUIZ_REVIEW_SCORES', 2*4161); // Show scores +define('QUIZ_REVIEW_FEEDBACK', 4*4161); // Show feedback +define('QUIZ_REVIEW_ANSWERS', 8*4161); // Show correct answers +// Some handling of worked solutions is already in the code but not yet fully supported +// and not switched on in the user interface. +define('QUIZ_REVIEW_SOLUTIONS', 16*4161); // Show solutions +// the 6th bit is as yet unused +/**#@-*/ + /**#@+ * The different types of events that can create question states */ @@ -52,13 +102,14 @@ define("QUIZ_MAX_NUMBER_ANSWERS", "10"); define("QUIZ_CATEGORIES_SORTORDER", "999"); + +/// QUIZ_QTYPES INITIATION ////////////////// + /** * Array holding question type objects */ $QUIZ_QTYPES= array(); -/// QUIZ_QTYPES INITIATION ////////////////// - require_once("$CFG->dirroot/mod/quiz/questiontypes/questiontype.php"); quiz_load_questiontypes(); @@ -84,6 +135,71 @@ function quiz_load_questiontypes() { } } +/// OTHER CLASSES ///////////////////////////////////////////////////////// + +/** +* This holds the options that are determined by the course module +*/ +class cmoptions { + /** + * Whether a new attempt should be based on the previous one. If true + * then a new attempt will start in a state where all responses are set + * to the last responses from the previous attempt. + */ + var $attemptonlast = false; + + /** + * Various option flags. The flags are accessed via bitwise operations + * using the constants defined in the CONSTANTS section above. + */ + var $optionflags = QUIZ_ADAPTIVE; + + /** + * Determines whether in the calculation of the score for a question + * penalties for earlier wrong responses within the same attempt will + * be subtracted. + */ + var $penaltyscheme = true; + + /** + * The maximum time the user is allowed to answer the questions withing + * an attempt. This is measured in minutes so needs to be multiplied by + * 60 before compared to timestamps. If set to 0 no timelimit will be applied + */ + var $timelimit = 0; + + /** + * Timestamp for the closing time. Responses submitted after this time will + * be saved but no credit will be given for them. + */ + var $timeclose = 9999999999; + + /** + * The id of the course from withing which the question is currently being used + */ + var $course = SITEID; + + /** + * Whether the answers in a multiple choice question should be randomly + * shuffled when a new attempt is started. + */ + var $shuffleanswers = false; + + /** + * The number of decimals to be shown when scores are printed + */ + var $decimalpoints = 2; + + /** + * Determines when a student is allowed to review. The information is read + * out from the bits with the help of the constants defined earlier + * We initialise this to allow the student to see everything (all bits set) + */ + var $review = 16777215; +} + + + /// FUNCTIONS ////////////////////////////////////////////////////// /** @@ -515,14 +631,15 @@ function quiz_regrade_question_in_attempt($question, $attempt, $cmoptions, $verb * @param object $cmoptions * @param object $attempt The attempt is passed by reference so that * during grading its ->sumgrades field can be updated -* -* @todo There is a variable $cmoptions->ignoredupresp which makes the function go through -* all previous states when checking if a response is duplicated. There is no user -* interface for this yet. */ function quiz_process_responses(&$question, &$state, $action, $cmoptions, &$attempt) { global $QUIZ_QTYPES; + // if no responses are set initialise to empty response + if (!isset($action->responses)) { + $action->responses = array('' => ''); + } + // make sure these are gone! unset($action->responses['mark'], $action->responses['validate']); @@ -678,12 +795,12 @@ function quiz_search_for_duplicate_responses(&$question, &$state) { } /** -* Applies the penalty from the previous attempts to the raw grade for the current -* attempt +* Applies the penalty from the previous graded responses to the raw grade +* for the current responses * * The grade for the question in the current state is computed by subtracting the -* penalty accumulated over the previous marked attempts at the question from the -* raw grade. If the timestamp is more than 1 minute beyond the start of the attempt +* penalty accumulated over the previous graded responses at the question from the +* raw grade. If the timestamp is more than 1 minute beyond the end of the attempt * the grade is set to zero. The ->grade field of the state object is modified to * reflect the new grade but is never allowed to decrease. * @param object $question The question for which the penalty is to be applied. @@ -692,8 +809,9 @@ function quiz_search_for_duplicate_responses(&$question, &$state) { * graded state. The ->grade field is updated by applying * the penalty scheme determined in $cmoptions to the ->raw_grade and * ->last_graded->penalty fields. -* @param object $cmoptions The options set by the course module. The penalty -* scheme to apply is given by the ->penaltyscheme field. +* @param object $cmoptions The options set by the course module. +* The ->penaltyscheme field determines whether penalties +* for incorrect earlier responses are subtracted. */ function quiz_apply_penalty_and_timelimit(&$question, &$state, $attempt, $cmoptions) { // deal with penaly @@ -818,6 +936,73 @@ function quiz_get_id_from_name_prefix($name) { return (integer) $matches[1]; } +function quiz_new_attempt_uniqueid() { + global $CFG; + set_config('attemptuniqueid', $CFG->attemptuniqueid + 1); + return $CFG->attemptuniqueid; +} + +/** +* Determine render options +*/ +function quiz_get_renderoptions($cmoptions, $state) { + // Show the question in readonly (review) mode if the question is in + // the closed state + $options->readonly = QUIZ_EVENTCLOSE === $state->event; + + // Show feedback once the question has been graded (if allowed by the quiz) + $options->feedback = ('' !== $state->grade) && ($cmoptions->review & QUIZ_REVIEW_FEEDBACK & QUIZ_REVIEW_IMMEDIATELY); + + // Show validation only after a validation event + $options->validation = QUIZ_EVENTVALIDATE === $state->event; + + // Show correct responses in readonly mode if the quiz allows it + $options->correct_responses = $options->readonly && ($cmoptions->review & QUIZ_REVIEW_ANSWERS & QUIZ_REVIEW_IMMEDIATELY); + + // Always show responses and scores + $options->responses = true; + $options->scores = true; + + return $options; +} + + +/** +* Determine review options +*/ +function quiz_get_reviewoptions($cmoptions, $attempt, $isteacher=false) { + $options->readonly = true; + if ($isteacher and !$attempt->preview) { + // The teacher should be shown everything except during preview when the teachers + // wants to see just what the students see + $options->responses = true; + $options->scores = true; + $options->feedback = true; + $options->correct_responses = true; + $options->solutions = false; + return $options; + } + if ((time() - $attempt->timefinish) < 120) { + $options->responses = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_RESPONSES) ? 1 : 0; + $options->scores = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_SCORES) ? 1 : 0; + $options->feedback = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; + $options->correct_responses = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; + $options->solutions = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; + } else if (time() < $cmoptions->timeclose) { + $options->responses = ($cmoptions->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_RESPONSES) ? 1 : 0; + $options->scores = ($cmoptions->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_SCORES) ? 1 : 0; + $options->feedback = ($cmoptions->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; + $options->correct_responses = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; + $options->solutions = ($cmoptions->review & QUIZ_REVIEW_OPEN & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; + } else { + $options->responses = ($cmoptions->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_RESPONSES) ? 1 : 0; + $options->scores = ($cmoptions->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_SCORES) ? 1 : 0; + $options->feedback = ($cmoptions->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_FEEDBACK) ? 1 : 0; + $options->correct_responses = ($cmoptions->review & QUIZ_REVIEW_IMMEDIATELY & QUIZ_REVIEW_ANSWERS) ? 1 : 0; + $options->solutions = ($cmoptions->review & QUIZ_REVIEW_CLOSED & QUIZ_REVIEW_SOLUTIONS) ? 1 : 0; + } + return $options; +} /// FUNCTIONS THAT ARE USED BY SOME QUESTIONTYPES /////////////////// diff --git a/mod/quiz/restorelib.php b/mod/quiz/restorelib.php index 4a5d25e570b..80e93ec2c41 100644 --- a/mod/quiz/restorelib.php +++ b/mod/quiz/restorelib.php @@ -1459,8 +1459,7 @@ } //Set the uniqueid field - $attempt->uniqueid = $CFG->attemptuniqueid; - set_config('attemptuniqueid', $CFG->attemptuniqueid + 1); + $attempt->uniqueid = quiz_new_attempt_uniqueid(); //We have to recode the layout field (a list of questions id and pagebreaks) $attempt->layout = quiz_recode_layout($attempt->layout, $restore); diff --git a/mod/quiz/restorelibpre15.php b/mod/quiz/restorelibpre15.php index dcdcc4c7c37..316b9d6a115 100644 --- a/mod/quiz/restorelibpre15.php +++ b/mod/quiz/restorelibpre15.php @@ -1463,8 +1463,7 @@ } //Set the uniqueid field - $attempt->uniqueid = $CFG->attemptuniqueid; - set_config('attemptuniqueid', $CFG->attemptuniqueid + 1); + $attempt->uniqueid = quiz_new_attempt_uniqueid(); //The structure is equal to the db, so insert the quiz_attempts $newid = insert_record ("quiz_attempts",$attempt);