prefix}quiz_attempts WHERE quiz = ? AND " . "userid = ? AND timefinish > 0 AND preview != 1", array($quiz->id, $USER->id))) { $attemptnumber = 1; } $strattemptnum = get_string('attempt', 'quiz', $attemptnumber); $strquizzes = get_string("modulenameplural", "quiz"); // course id: $course->id // course id: $course->id // course name: $quiz->id // attempts: $quiz->attempts if ($limittoquestion == 0) $output[] = array('course',$course->id,$course->fullname); $numberofpreviousattempts = sloodle_count_records_select_params('quiz_attempts', "quiz = ? AND " . "userid = ? AND timefinish > 0 AND preview != 1", array($quiz->id, $USER->id)); if ($quiz->attempts and $numberofpreviousattempts >= $quiz->attempts) { $sloodle->response->quick_output(-10301, 'QUIZ', 'You do not have any attempts left', FALSE); exit(); } /// Check subnet access if ($quiz->subnet and !address_in_subnet(getremoteaddr(), $quiz->subnet)) { $sloodle->response->quick_output(-1, 'MISC', 'A subnet error occurred', FALSE); exit(); } /// Check password access if ($quiz->password) { $sloodle->response->quick_output(-10302, 'QUIZ', 'Quiz requires password - not supported by Sloodle', FALSE); exit(); } if ($quiz->delay1 or $quiz->delay2) { //quiz enforced time delay if ($attempts = quiz_get_user_attempts($quiz->id, $USER->id)) { $numattempts = count($attempts); } else { $numattempts = 0; } $timenow = time(); $lastattempt_obj = sloodle_get_record_select_params('quiz_attempts', "quiz = ? AND attempt = ? AND userid = ?", array($quiz->id, $numattempts, $USER->id), 'timefinish'); if ($lastattempt_obj) { $lastattempt = $lastattempt_obj->timefinish; } if ($numattempts == 1 && $quiz->delay1) { if ($timenow - $quiz->delay1 < $lastattempt) { $sloodle->response->quick_output(-701, 'QUIZ', 'You need to wait until the time delay has expired.', FALSE); exit(); } } else if($numattempts > 1 && $quiz->delay2) { if ($timenow - $quiz->delay2 < $lastattempt) { $sloodle->response->quick_output(-701, 'QUIZ', 'You need to wait until the time delay has expired.', FALSE); exit(); } } } // sloodle_prim_render_output($output); // exit; /// Load attempt or create a new attempt if there is no unfinished one $attempt = sloodle_get_record('quiz_attempts', 'quiz', $quiz->id, 'userid', $USER->id, 'timefinish', 0); $newattempt = false; if (!$attempt) { $newattempt = true; // Start a new attempt and initialize the question sessions $attempt = quiz_create_attempt($quiz, $attemptnumber); // If this is an attempt by a teacher mark it as a preview // Save the attempt if (!$attempt->timestart) { // shouldn't really happen, just for robustness $attempt->timestart = time(); } if (!$attempt->timemodified) { // shouldn't really happen, just for robustness $attempt->timemodified = time(); } if (!$attempt->id = sloodle_insert_record('quiz_attempts', $attempt)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Could not create new attempt.', FALSE); exit(); } // make log entries add_to_log($course->id, 'quiz', 'attempt', "review.php?attempt=$attempt->id", "$quiz->id", $cm->id); } else { // log continuation of attempt only if some time has lapsed if (($timestamp - $attempt->timemodified) > 600) { // 10 minutes have elapsed add_to_log($course->id, 'quiz', 'continue attemp', // this action used to be called 'continue attempt' but the database field has only 15 characters "review.php?attempt=$attempt->id", "$quiz->id", $cm->id); } } if (!$attempt->timestart) { // shouldn't really happen, just for robustness $attempt->timestart = time(); } /// Load all the questions and states needed by this script ///// SLOODLE MODIFICATION ///// // For SLOODLE to work properly, it needs the entire list of questions at all times. // It ignores the "page" structure of a Moodle quiz. $questionlist = quiz_questions_in_quiz($attempt->layout); $pagelist = $questionlist; ///// END SLOODLE MODIFICATION ///// $questionlistids = explode(',', $questionlist); if ($questionids != '') { $questionids = explode(',', $questionids); } // add all questions that are on the submitted form if ($questionids && (count($questionids) > 0) ) { $questionlistids = array_merge($questionlistids, $questionids); } $params = array($quiz->id); $questioninstr = ''; $delim = ''; foreach($questionlistids as $qlid) { $params[] = $qlid; $questioninstr .= $delim.'?'; $delim = ','; } if ( !$questionlistids || (count($questionlistids) == 0) ) { $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.', FALSE); exit(); } $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance". " FROM {$CFG->prefix}question q,". " {$CFG->prefix}quiz_question_instances i". " WHERE i.quiz = ? AND q.id = i.question". " AND q.id IN ($questioninstr) ;"; // Load the questions $questions = sloodle_get_records_sql_params($sql, $params); if ( !$questions || (count($questions) == 0) ) { $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.'.$sql.join(':',$params), FALSE); exit; $sloodle->response->quick_output(-10303, 'QUIZ', 'No questions found.', FALSE); exit(); } // Load the question type specific information if (!get_question_options($questions)) { $sloodle->response->quick_output(-10303, 'QUIZ', 'Could not load question options.', FALSE); exit(); } // Restore the question sessions to their most recent states // creating new sessions where required if (!$states = get_question_states($questions, $quiz, $attempt)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Could not restore questions sessions.', FALSE); exit(); } // Save all the newly created states if ($newattempt) { foreach ($questions as $i => $question) { save_question_session($questions[$i], $states[$i]); } } // If the new attempt is to be based on a previous attempt copy responses over if ($newattempt and $attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) { // Find the previous attempt if (!$lastattemptid = sloodle_get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Could not find previous attempt to build on.', FALSE); exit(); } // For each question find the responses from the previous attempt and save them to the new session foreach ($questions as $i => $question) { // Load the last graded state for the question $statefields = 'n.questionid as question, s.*, n.sumpenalty'; $sql = "SELECT $statefields". " FROM {$CFG->prefix}question_states s,". " {$CFG->prefix}question_sessions n". " WHERE s.id = n.newgraded". " AND n.attemptid = ?". " AND n.questionid = ?"; if (!$laststate = sloodle_get_record_sql_params($sql, array($lastattemptid, $i))) { // Only restore previous responses that have been graded continue; } // Restore the state so that the responses will be restored restore_question_state($questions[$i], $laststate); // prepare the previous responses for new processing $action = new stdClass; $action->responses = $laststate->responses; $action->timestamp = $laststate->timestamp; $action->event = QUESTION_EVENTOPEN; // Process these responses ... question_process_responses($questions[$i], $states[$i], $action, $quiz, $attempt); // Fix for Bug #5506: When each attempt is built on the last one, // preserve the options from any previous attempt. if ( isset($laststate->options) ) { $states[$i]->options = $laststate->options; } // ... and save the new states save_question_session($questions[$i], $states[$i]); } } /// Process form data ///////////////////////////////////////////////// if ($isnotify) { SloodleDebugLogger::log('DEBUG', "in is notify"); $responses = (object)$_REQUEST; // GET version of data_submitted (see lib/weblib) used in original web version // set the default event. This can be overruled by individual buttons. $event = (array_key_exists('markall', $responses)) ? QUESTION_EVENTSUBMIT : ($finishattempt ? QUESTION_EVENTCLOSE : QUESTION_EVENTSAVE); SloodleDebugLogger::log('DEBUG', "checked finish attempt"); // Unset any variables we know are not responses unset($responses->id); unset($responses->q); unset($responses->oldpage); unset($responses->newpage); unset($responses->review); unset($responses->questionids); unset($responses->saveattempt); // responses get saved anway unset($responses->finishattempt); // same as $finishattempt unset($responses->markall); unset($responses->forcenewattempt); // extract responses // $actions is an array indexed by the questions ids $actions = question_extract_responses($questions, $responses, $event); SloodleDebugLogger::log('DEBUG', "extracted"); // Process each question in turn foreach($questionids as $i) { if (!isset($actions[$i])) { $actions[$i]->responses = array('' => ''); } $actions[$i]->timestamp = $timestamp; question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt); save_question_session($questions[$i], $states[$i]); } $attempt->timemodified = $timestamp; // We have now finished processing form data // With post sloodle-2.0 quiz chairs, we should be told what happened to the score. // In theory we should be able to get this from the data we already have, but it seems complex... // The process_events allows us to award points if there is an instruction to do so in the object config. $scorechange = floatval(optional_param( 'scorechange', 0, PARAM_TEXT)); //SloodleDebugLogger::log('DEBUG', "active object check"); if (!is_null($sloodle->active_object)) { //SloodleDebugLogger::log('DEBUG', "quiz has an active object"); $sloodle->process_interaction('answerquestion', $scorechange); /* if ($scorechange > 0) { } else if ($scorechange < 0) { //$sloodle->active_object->process_events( 'SloodleModuleAwards', 'answerincorrect', 1, $sloodle->user->get_user_id() ); } */ // TODO: Maybe we should set a side effect code here? } else { SloodleDebugLogger::log('DEBUG', "quiz has no active object"); } } else { SloodleDebugLogger::log('DEBUG', 'quiz is not notify'); } /// Finish attempt if requested if ($finishattempt) { // Set the attempt to be finished $attempt->timefinish = $timestamp; // Find all the questions for this attempt for which the newest // state is not also the newest graded state if ($closequestions = sloodle_get_records_select_params('question_sessions', "attemptid = ? AND newest != newgraded", array($attempt->uniqueid),'', 'questionid, questionid')) { // load all the questions $closequestionlist = implode(',', array_keys($closequestions)); $params = array($quiz->id); $instr = ''; $delim = ''; foreach(array_keys($closequestions) as $cq) { $params[] = $cq; $instr .= $delim.'?'; $delim = ','; } $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance". " FROM {$CFG->prefix}question q,". " {$CFG->prefix}quiz_question_instances i". " WHERE i.quiz = ? AND q.id = i.question". " AND q.id IN ($instr)"; if (!$closequestions = sloodle_get_records_sql_params($sql, $params)) { $sloodle->response->quick_output(-10303, 'QUIZ', 'Questions missing.', FALSE); exit(); } // Load the question type specific information if (!get_question_options($closequestions)) { $sloodle->response->quick_output(-10303, 'QUIZ', 'Could not load question options.', FALSE); exit(); } // Restore the question sessions if (!$closestates = get_question_states($closequestions, $quiz, $attempt)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Could not restore question sessions.', FALSE); exit(); } foreach($closequestions as $key => $question) { $action->event = QUESTION_EVENTCLOSE; $action->responses = $closestates[$key]->responses; $action->timestamp = $closestates[$key]->timestamp; question_process_responses($question, $closestates[$key], $action, $quiz, $attempt); save_question_session($question, $closestates[$key]); } } add_to_log($course->id, 'quiz', 'close attempt', "review.php?attempt=$attempt->id", "$quiz->id", $cm->id); } /// Update the quiz attempt and the overall grade for the quiz if ((isset($responses) && $responses) || $finishattempt) { if (!sloodle_update_record('quiz_attempts', $attempt)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Failed to save current quiz attempt.', FALSE); exit(); } if (($attempt->attempt > 1 || $attempt->timefinish > 0) and !$attempt->preview) { quiz_save_best_grade($quiz); } } /// Check access to quiz page // check the quiz times //TODO: Figure out what this does... if ($timestamp < $quiz->timeopen || ($quiz->timeclose and $timestamp > $quiz->timeclose)) { $sloodle->response->quick_output(-701, 'QUIZ', 'Quiz not available.', FALSE); exit(); } if ($finishattempt) { // redirect('review.php?attempt='.$attempt->id); //$sloodle->response->quick_output(-701, 'QUIZ', 'Got to finishattempt - but do not yet have Sloodle code to handle it.', FALSE); //exit(); } /// Print the quiz page //////////////////////////////////////////////////////// if (!$isnotify) { $pagequestions = explode(',', $pagelist); $lastquestion = count($pagequestions); // Only output quiz data if a question has not been requetsed if ($limittoquestion == 0) { $output[] = array('quiz',$quiz->attempts,$quiz->name,$quiz->timelimit,$quiz->id,$lastquestion); $output[] = array('quizpages',quiz_number_of_pages($attempt->layout),$page,$pagelist); } // We will keep track of question numbers local to this quiz $localqnum = 0; /// Print all the questions $number = quiz_first_questionnumber($attempt->layout, $pagelist); foreach ($pagequestions as $i) { $options = quiz_get_renderoptions($quiz->review, $states[$i]); // Print the question // var_dump($questions[$i]); $q = $questions[$i]; $localqnum++; if ($limittoquestion == $q->id) { //echo "