if (!$success) { $pagebit = ''; if ($page) { $pagebit = '&page=' . $page; } print_error('errorprocessingresponses', 'question', $CFG->wwwroot . '/mod/quiz/attempt.php?q=' . $quiz->id . $pagebit); } 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 ($responses || $finishattempt) { if (!update_record('quiz_attempts', $attempt)) { error('Failed to save the current quiz attempt!'); } if ($attempt->attempt > 1 || $attempt->timefinish > 0 and !$attempt->preview) { quiz_save_best_grade($quiz); } } /// Send emails to those who have the capability set if ($finishattempt && !$attempt->preview) { quiz_send_notification_emails($course, $quiz, $attempt, $context, $cm); } if ($finishattempt) { if (!empty($SESSION->passwordcheckedquizzes[$quiz->id])) { unset($SESSION->passwordcheckedquizzes[$quiz->id]); } redirect($CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id, 0); } // Now is the right time to check the open and close times. if (!$ispreviewing && ($timestamp < $quiz->timeopen || $quiz->timeclose && $timestamp > $quiz->timeclose)) { print_error('notavailable', 'quiz', "view.php?id={$cm->id}");
/** * Displays the report. */ function display($quiz, $cm, $course) { global $CFG, $QTYPES; $viewoptions = array('mode' => 'grading', 'q' => $quiz->id); if ($questionid = optional_param('questionid', 0, PARAM_INT)) { $viewoptions += array('questionid' => $questionid); } // grade question specific parameters $gradeungraded = optional_param('gradeungraded', 0, PARAM_INT); if ($userid = optional_param('userid', 0, PARAM_INT)) { $viewoptions += array('userid' => $userid); } if ($attemptid = optional_param('attemptid', 0, PARAM_INT)) { $viewoptions += array('attemptid' => $attemptid); } if ($gradeall = optional_param('gradeall', 0, PARAM_INT)) { $viewoptions += array('gradeall' => $gradeall); } if ($gradeungraded = optional_param('gradeungraded', 0, PARAM_INT)) { $viewoptions += array('gradeungraded' => $gradeungraded); } if ($gradenextungraded = optional_param('gradenextungraded', 0, PARAM_INT)) { $viewoptions += array('gradenextungraded' => $gradenextungraded); } $this->cm = $cm; $this->print_header_and_tabs($cm, $course, $quiz, $reportmode = "grading"); // Check permissions $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); if (!has_capability('mod/quiz:grade', $this->context)) { notify(get_string('gradingnotallowed', 'quiz_grading')); return true; } $gradeableqs = quiz_report_load_questions($quiz); $questionsinuse = implode(',', array_keys($gradeableqs)); foreach ($gradeableqs as $qid => $question) { if (!$QTYPES[$question->qtype]->is_question_manual_graded($question, $questionsinuse)) { unset($gradeableqs[$qid]); } } if (empty($gradeableqs)) { print_heading(get_string('noessayquestionsfound', 'quiz')); return true; } else { if (count($gradeableqs) == 1) { $questionid = array_shift(array_keys($gradeableqs)); } } $currentgroup = groups_get_activity_group($this->cm, true); $this->users = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', $currentgroup, '', false); $this->userids = implode(',', array_keys($this->users)); if (!empty($questionid)) { if (!isset($gradeableqs[$questionid])) { error("Gradeable question with id {$questionid} not found"); } else { $question =& $gradeableqs[$questionid]; } $question->maxgrade = get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id, 'question', $question->id); // Some of the questions code is optimised to work with several questions // at once so it wants the question to be in an array. The array key // must be the question id. $key = $question->id; $questions[$key] =& $question; // We need to add additional questiontype specific information to // the question objects. if (!get_question_options($questions)) { error("Unable to load questiontype specific question information"); } // This will have extended the question object so that it now holds // all the information about the questions that may be needed later. } add_to_log($course->id, "quiz", "manualgrading", "report.php?mode=grading&q={$quiz->id}", "{$quiz->id}", "{$cm->id}"); echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib if ($data = data_submitted()) { // post data submitted, process it require_sesskey(); // now go through all of the responses and save them. $allok = true; foreach ($data->manualgrades as $uniqueid => $response) { // get our attempt $uniqueid = clean_param($uniqueid, PARAM_INT); if (!($attempt = get_record_sql("SELECT * FROM {$CFG->prefix}quiz_attempts " . "WHERE uniqueid = {$uniqueid} AND " . "userid IN ({$this->userids}) AND " . "quiz=" . $quiz->id))) { error('No such attempt ID exists'); } // Load the state for this attempt (The questions array was created earlier) $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; // the following will update the state and attempt $error = question_process_comment($question, $state, $attempt, $response['comment'], $response['grade']); if (is_string($error)) { notify($error); $allok = false; } else { if ($state->changed) { // If the state has changed save it and update the quiz grade save_question_session($question, $state); quiz_save_best_grade($quiz, $attempt->userid); } } } if ($allok) { notify(get_string('changessaved', 'quiz'), 'notifysuccess'); } else { notify(get_string('changessavedwitherrors', 'quiz'), 'notifysuccess'); } } $this->viewurl = new moodle_url($CFG->wwwroot . '/mod/quiz/report.php', $viewoptions); /// find out current groups mode if ($groupmode = groups_get_activity_groupmode($this->cm)) { // Groups are being used groups_print_activity_menu($this->cm, $this->viewurl->out(false, array('userid' => 0, 'attemptid' => 0))); } echo '<div class="quizattemptcounts">' . quiz_num_attempt_summary($quiz, $cm, true, $currentgroup) . '</div>'; if (empty($this->users)) { if ($currentgroup) { notify(get_string('nostudentsingroup')); } else { notify(get_string('nostudentsyet')); } return true; } $gradeablequestionids = implode(',', array_keys($gradeableqs)); $qattempts = quiz_get_total_qas_graded_and_ungraded($quiz, $gradeablequestionids, $this->userids); if (empty($qattempts)) { notify(get_string('noattemptstoshow', 'quiz')); return true; } $qmenu = array(); foreach ($gradeableqs as $qid => $questionformenu) { $a = new object(); $a->number = $gradeableqs[$qid]->number; $a->name = $gradeableqs[$qid]->name; $a->gradedattempts = $qattempts[$qid]->gradedattempts; $a->totalattempts = $qattempts[$qid]->totalattempts; $a->openspan = ''; $a->closespan = ''; $qmenu[$qid] = get_string('questiontitle', 'quiz_grading', $a); } if (count($gradeableqs) != 1) { $qurl = fullclone($this->viewurl); $qurl->remove_params('questionid', 'attemptid', 'gradeall', 'gradeungraded', 'gradenextungraded'); $menu = popup_form($qurl->out() . '&questionid=', $qmenu, 'questionid', $questionid, 'choose', '', '', true); echo '<div class="mdl-align">' . $menu . '</div>'; } if (!$questionid) { return true; } $a = new object(); $a->number = $question->number; $a->name = $question->name; $a->gradedattempts = $qattempts[$question->id]->gradedattempts; $a->totalattempts = $qattempts[$question->id]->totalattempts; $a->openspan = '<span class="highlightgraded">'; $a->closespan = '</span>'; print_heading(get_string('questiontitle', 'quiz_grading', $a)); // our 3 different views // the first one displays all of the manually graded questions in the quiz // with the number of ungraded attempts for each question // the second view displays the users who have answered the essay question // and all of their attempts at answering the question // the third prints the question with a comment // and grade form underneath it $ungraded = $qattempts[$questionid]->totalattempts - $qattempts[$questionid]->gradedattempts; if ($gradenextungraded || $gradeungraded || $gradeall || $userid || $attemptid) { $this->print_questions_and_form($quiz, $question, $userid, $attemptid, $gradeungraded, $gradenextungraded, $ungraded); } else { $this->view_question($quiz, $question, $qattempts[$questionid]->totalattempts, $ungraded); } return true; }
/** * Delete a quiz attempt. * @param mixed $attempt an integer attempt id or an attempt object * (row of the quiz_attempts table). * @param object $quiz the quiz object. */ function quiz_delete_attempt($attempt, $quiz) { global $DB; if (is_numeric($attempt)) { if (!$attempt = $DB->get_record('quiz_attempts', array('id' => $attempt))) { return; } } if ($attempt->quiz != $quiz->id) { debugging("Trying to delete attempt $attempt->id which belongs to quiz $attempt->quiz " . "but was passed quiz $quiz->id."); return; } question_engine::delete_questions_usage_by_activity($attempt->uniqueid); $DB->delete_records('quiz_attempts', array('id' => $attempt->id)); // Search quiz_attempts for other instances by this user. // If none, then delete record for this quiz, this user from quiz_grades // else recalculate best grade. $userid = $attempt->userid; if (!$DB->record_exists('quiz_attempts', array('userid' => $userid, 'quiz' => $quiz->id))) { $DB->delete_records('quiz_grades', array('userid' => $userid, 'quiz' => $quiz->id)); } else { quiz_save_best_grade($quiz, $userid); } quiz_update_grades($quiz, $userid); }
// Some of the questions code is optimised to work with several questions // at once so it wants the question to be in an array. $key = $question->id; $questions[$key] =& $question; // Add additional questiontype specific information to the question objects. if (!get_question_options($questions)) { error("Unable to load questiontype specific question information"); } // Load state $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; print_header(); print_heading(format_string($question->name)); //add_to_log($course->id, 'quiz', 'review', "review.php?id=$cm->id&attempt=$attempt->id", "$quiz->id", "$cm->id"); if ($data = data_submitted() and confirm_sesskey()) { // the following will update the state and attempt question_process_comment($question, $state, $attempt, $data->response['comment'], $data->response['grade']); // If the state has changed save it and update the quiz grade if ($state->changed) { save_question_session($question, $state); quiz_save_best_grade($quiz, $attempt->userid); } notify(get_string('changessaved')); echo '<div class="boxaligncenter"><input type="button" onclick="window.opener.location.reload(1); self.close();return false;" value="' . get_string('closewindow') . "\" /></div>"; print_footer(); exit; } question_print_comment_box($question, $state, $attempt, $CFG->wwwroot . '/mod/quiz/comment.php'); print_footer();
function display($quiz, $cm, $course) { global $CFG; // Print header $this->print_header_and_tabs($cm, $course, $quiz, $reportmode = "regrade"); // Check permissions $context = get_context_instance(CONTEXT_MODULE, $cm->id); if (!has_capability('mod/quiz:grade', $context)) { notify(get_string('regradenotallowed', 'quiz')); return true; } // 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}question q,\n {$CFG->prefix}quiz_question_instances i\n WHERE i.quiz = {$quiz->id}\n AND i.question = q.id"; if (!($questions = get_records_sql($sql))) { error("Failed to get questions for regrading!"); } get_question_options($questions); // Print heading print_heading(get_string('regradingquiz', 'quiz', format_string($quiz->name))); echo '<div class="boxaligncenter">'; print_string('regradedisplayexplanation', 'quiz'); echo '</div>'; // Loop through all questions and all attempts and regrade while printing progress info foreach ($questions as $question) { echo '<strong>' . get_string('regradingquestion', 'quiz', $question->name) . '</strong> ' . get_string('attempts', 'quiz') . ": \n"; foreach ($attempts as $attempt) { set_time_limit(30); $changed = regrade_question_in_attempt($question, $attempt, $quiz, true); 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')); } else { echo ' #' . $attempt->id; } } 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('question_sessions', 'newgraded', 'attemptid', $attempt->uniqueid, 'questionid', $questionid); $sumgrades += get_field('question_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; }
/** * Displays the report. */ function display($quiz, $cm, $course) { global $CFG, $QTYPES, $DB, $OUTPUT, $PAGE; $viewoptions = array('mode' => 'grading', 'q' => $quiz->id); if ($questionid = optional_param('questionid', 0, PARAM_INT)) { $viewoptions += array('questionid' => $questionid); } // grade question specific parameters if ($userid = optional_param('userid', 0, PARAM_INT)) { $viewoptions += array('userid' => $userid); } if ($attemptid = optional_param('attemptid', 0, PARAM_INT)) { $viewoptions += array('attemptid' => $attemptid); } if ($gradeall = optional_param('gradeall', 0, PARAM_INT)) { $viewoptions += array('gradeall' => $gradeall); } if ($gradeungraded = optional_param('gradeungraded', 0, PARAM_INT)) { $viewoptions += array('gradeungraded' => $gradeungraded); } if ($gradenextungraded = optional_param('gradenextungraded', 0, PARAM_INT)) { $viewoptions += array('gradenextungraded' => $gradenextungraded); } $this->cm = $cm; $this->print_header_and_tabs($cm, $course, $quiz, $reportmode = "grading"); // Check permissions $this->context = get_context_instance(CONTEXT_MODULE, $cm->id); if (!has_capability('mod/quiz:grade', $this->context)) { echo $OUTPUT->notification(get_string('gradingnotallowed', 'quiz_grading')); return true; } $gradeableqs = quiz_report_load_questions($quiz); $questionsinuse = implode(',', array_keys($gradeableqs)); foreach ($gradeableqs as $qid => $question) { if (!$QTYPES[$question->qtype]->is_question_manual_graded($question, $questionsinuse)) { unset($gradeableqs[$qid]); } } if (empty($gradeableqs)) { echo $OUTPUT->heading(get_string('noessayquestionsfound', 'quiz')); return true; } else { if (count($gradeableqs) == 1) { $questionid = array_shift(array_keys($gradeableqs)); } } $currentgroup = groups_get_activity_group($this->cm, true); $this->users = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), '', '', '', '', $currentgroup, '', false); if (!empty($questionid)) { if (!isset($gradeableqs[$questionid])) { print_error('invalidquestionid', 'quiz_grading', '', $questionid); } else { $question =& $gradeableqs[$questionid]; } // Some of the questions code is optimised to work with several questions // at once so it wants the question to be in an array. The array key // must be the question id. $key = $question->id; $questions[$key] =& $question; // We need to add additional questiontype specific information to // the question objects. if (!get_question_options($questions)) { print_error('cannotloadquestioninfo', 'quiz_grading'); } // This will have extended the question object so that it now holds // all the information about the questions that may be needed later. } add_to_log($course->id, "quiz", "manualgrading", "report.php?mode=grading&q={$quiz->id}", "{$quiz->id}", "{$cm->id}"); if ($data = data_submitted()) { // post data submitted, process it if (confirm_sesskey() && $this->users) { // now go through all of the responses and save them. $allok = true; foreach ($data->manualgrades as $uniqueid => $response) { // get our attempt $uniqueid = clean_param($uniqueid, PARAM_INT); list($usql, $params) = $DB->get_in_or_equal(array_keys($this->users)); if (!($attempt = $DB->get_record_sql("SELECT * FROM {quiz_attempts} " . "WHERE uniqueid = ? AND " . "userid {$usql} AND " . "quiz=?", array_merge(array($uniqueid), $params, array($quiz->id))))) { print_error('invalidattemptid', 'quiz_grading'); } // Load the state for this attempt (The questions array was created earlier) $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; // the following will update the state and attempt $error = question_process_comment($question, $state, $attempt, $response['comment'], FORMAT_HTML, $response['grade']); if (is_string($error)) { echo $OUTPUT->notification($error); $allok = false; } else { if ($state->changed) { // If the state has changed save it and update the quiz grade save_question_session($question, $state); quiz_save_best_grade($quiz, $attempt->userid); } } } if ($allok) { echo $OUTPUT->notification(get_string('changessaved', 'quiz'), 'notifysuccess'); } else { echo $OUTPUT->notification(get_string('changessavedwitherrors', 'quiz'), 'notifysuccess'); } } } $this->viewurl = new moodle_url('/mod/quiz/report.php', $viewoptions); /// find out current groups mode if ($groupmode = groups_get_activity_groupmode($this->cm)) { // Groups are being used groups_print_activity_menu($this->cm, $this->viewurl->out(true, array('userid' => 0, 'attemptid' => 0))); } if (empty($this->users)) { if ($currentgroup) { echo $OUTPUT->notification(get_string('nostudentsingroup')); } else { echo $OUTPUT->notification(get_string('nostudentsyet')); } return true; } $qattempts = quiz_get_total_qas_graded_and_ungraded($quiz, array_keys($gradeableqs), array_keys($this->users)); if (empty($qattempts)) { echo $OUTPUT->notification(get_string('noattemptstoshow', 'quiz')); return true; } $qmenu = array(); foreach ($gradeableqs as $qid => $questionformenu) { $a = new stdClass(); $a->number = $gradeableqs[$qid]->number; $a->name = $gradeableqs[$qid]->name; $a->gradedattempts = $qattempts[$qid]->gradedattempts; $a->totalattempts = $qattempts[$qid]->totalattempts; $a->openspan = ''; $a->closespan = ''; $qmenu[$qid] = get_string('questiontitle', 'quiz_grading', $a); } if (count($gradeableqs) != 1) { $qurl = fullclone($this->viewurl); $qurl->remove_params('questionid', 'attemptid', 'gradeall', 'gradeungraded', 'gradenextungraded'); $menu = $OUTPUT->single_select($qurl, 'questionid', $qmenu, $questionid, array('' => 'choosedots'), 'questionid'); echo '<div class="mdl-align">' . $menu . '</div>'; } if (!$questionid) { return true; } $a = new stdClass(); $a->number = $question->number; $a->name = $question->name; $a->gradedattempts = $qattempts[$question->id]->gradedattempts; $a->totalattempts = $qattempts[$question->id]->totalattempts; $a->openspan = '<span class="highlightgraded">'; $a->closespan = '</span>'; echo $OUTPUT->heading(get_string('questiontitle', 'quiz_grading', $a)); // our 2 different views // the first view allows a user to select a question and // displays the users who have answered the essay question // and all of their attempts at answering the question // the second prints selected attempt answer(s) with a comment // and grade form underneath them $ungraded = $qattempts[$questionid]->totalattempts - $qattempts[$questionid]->gradedattempts; if ($gradenextungraded || $gradeungraded || $gradeall || $userid || $attemptid) { $this->print_questions_and_form($quiz, $question, $userid, $attemptid, $gradeungraded, $gradenextungraded, $ungraded); } else { $this->view_question($quiz, $question, $qattempts[$questionid]->totalattempts, $ungraded); } return true; }
function check_overall_grades($quiz, $userids=array(), $attemptids=array()) { global $DB; //recalculate $attempt->sumgrade //already updated in regrade_question_in_attempt $sql = "UPDATE {quiz_attempts} SET sumgrades= " . "COALESCE((SELECT SUM(qs.grade) FROM {question_sessions} qns, {question_states} qs " . "WHERE qns.newgraded = qs.id AND qns.attemptid = {quiz_attempts}.uniqueid ), 0) WHERE "; $attemptsql=''; if (!$attemptids) { if ($userids) { list($usql, $params) = $DB->get_in_or_equal($userids); $attemptsql .= "{quiz_attempts}.userid $usql AND "; } else { $params = array(); } $attemptsql .= "{quiz_attempts}.quiz =? AND preview = 0"; $params[] = $quiz->id; } else { list($asql, $params) = $DB->get_in_or_equal($attemptids); $attemptsql .= "{quiz_attempts}.uniqueid $asql"; } $sql .= $attemptsql; if (!$DB->execute($sql, $params)) { print_error('err_failedtorecalculateattemptgrades', 'quiz_overview'); } // Update the overall quiz grades if ($attemptids) { //make sure we fetch all attempts for users to calculate grade. //not just those that have changed. $sql = "SELECT qa2.* FROM {quiz_attempts} qa2 WHERE " . "qa2.userid IN (SELECT DISTINCT userid FROM {quiz_attempts} WHERE $attemptsql) " . "AND qa2.timefinish > 0"; } else { $sql = "SELECT * FROM {quiz_attempts} WHERE $attemptsql AND timefinish > 0"; } if ($attempts = $DB->get_records_sql($sql, $params)) { $attemptsbyuser = quiz_report_index_by_keys($attempts, array('userid', 'id')); foreach($attemptsbyuser as $userid => $attemptsforuser) { quiz_save_best_grade($quiz, $userid, $attemptsforuser); } } }
public function finish_attempt($timestamp) { global $DB, $USER; $this->quba->process_all_actions($timestamp); $this->quba->finish_all_questions($timestamp); question_engine::save_questions_usage_by_activity($this->quba); $this->attempt->timemodified = $timestamp; $this->attempt->timefinish = $timestamp; $this->attempt->sumgrades = $this->quba->get_total_mark(); $DB->update_record('quiz_attempts', $this->attempt); if (!$this->is_preview()) { quiz_save_best_grade($this->get_quiz()); // Trigger event $eventdata = new stdClass(); $eventdata->component = 'mod_quiz'; $eventdata->attemptid = $this->attempt->id; $eventdata->timefinish = $this->attempt->timefinish; $eventdata->userid = $this->attempt->userid; $eventdata->submitterid = $USER->id; $eventdata->quizid = $this->get_quizid(); $eventdata->cmid = $this->get_cmid(); $eventdata->courseid = $this->get_courseid(); events_trigger('quiz_attempt_submitted', $eventdata); // Clear the password check flag in the session. $this->get_access_manager($timestamp)->clear_password_access(); } }
/** * Delete a quiz attempt. * @param mixed $attempt an integer attempt id or an attempt object * (row of the quiz_attempts table). * @param object $quiz the quiz object. */ function quiz_delete_attempt($attempt, $quiz) { global $DB; if (is_numeric($attempt)) { if (!($attempt = $DB->get_record('quiz_attempts', array('id' => $attempt)))) { return; } } if ($attempt->quiz != $quiz->id) { debugging("Trying to delete attempt {$attempt->id} which belongs to quiz {$attempt->quiz} " . "but was passed quiz {$quiz->id}."); return; } if (!isset($quiz->cmid)) { $cm = get_coursemodule_from_instance('quiz', $quiz->id, $quiz->course); $quiz->cmid = $cm->id; } question_engine::delete_questions_usage_by_activity($attempt->uniqueid); $DB->delete_records('quiz_attempts', array('id' => $attempt->id)); // Log the deletion of the attempt. $params = array('objectid' => $attempt->id, 'relateduserid' => $attempt->userid, 'context' => context_module::instance($quiz->cmid), 'other' => array('quizid' => $quiz->id)); $event = \mod_quiz\event\attempt_deleted::create($params); $event->add_record_snapshot('quiz_attempts', $attempt); $event->trigger(); // Search quiz_attempts for other instances by this user. // If none, then delete record for this quiz, this user from quiz_grades // else recalculate best grade. $userid = $attempt->userid; if (!$DB->record_exists('quiz_attempts', array('userid' => $userid, 'quiz' => $quiz->id))) { $DB->delete_records('quiz_grades', array('userid' => $userid, 'quiz' => $quiz->id)); } else { quiz_save_best_grade($quiz, $userid); } quiz_update_grades($quiz, $userid); }
/** * Display the report. */ function display($quiz, $cm, $course) { global $CFG, $SESSION, $db, $QTYPES; // Define some strings $strreallydel = addslashes(get_string('deleteattemptcheck', 'quiz')); $strtimeformat = get_string('strftimedatetime'); $strreviewquestion = get_string('reviewresponse', 'quiz'); $context = get_context_instance(CONTEXT_MODULE, $cm->id); // Only print headers if not asked to download data if (!($download = optional_param('download', NULL))) { $this->print_header_and_tabs($cm, $course, $quiz, $reportmode = "overview"); } // Deal with actions $action = optional_param('action', '', PARAM_ACTION); switch ($action) { case 'delete': // Some attempts need to be deleted require_capability('mod/quiz:deleteattempts', $context); $attemptids = optional_param('attemptid', array(), PARAM_INT); foreach ($attemptids as $attemptid) { if ($attemptid && ($todelete = get_record('quiz_attempts', 'id', $attemptid))) { add_to_log($course->id, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id, $attemptid, $cm->id); delete_records('quiz_attempts', 'id', $attemptid); delete_attempt($todelete->uniqueid); // Search quiz_attempts for other instances by this user. // If none, then delete record for this quiz, this user from quiz_grades // else recalculate best grade $userid = $todelete->userid; if (!record_exists('quiz_attempts', 'userid', $userid, 'quiz', $quiz->id)) { delete_records('quiz_grades', 'userid', $userid, 'quiz', $quiz->id); } else { quiz_save_best_grade($quiz, $userid); } } } break; } // Set of format options for teacher-created content, for example overall feedback. $nocleanformatoptions = new stdClass(); $nocleanformatoptions->noclean = true; // Set table options $noattempts = optional_param('noattempts', 0, PARAM_INT); $detailedmarks = optional_param('detailedmarks', 0, PARAM_INT); $pagesize = optional_param('pagesize', 10, PARAM_INT); $reporturl = $CFG->wwwroot . '/mod/quiz/report.php?mode=overview'; $reporturlwithoptions = $reporturl . '&id=' . $cm->id . '&noattempts=' . $noattempts . '&detailedmarks=' . $detailedmarks . '&pagesize=' . $pagesize; // Print information on the number of existing attempts if (!$download) { //do not print notices when downloading if ($attemptnum = count_records('quiz_attempts', 'quiz', $quiz->id, 'preview', 0)) { $a = new stdClass(); $a->attemptnum = $attemptnum; $a->studentnum = count_records_select('quiz_attempts', "quiz = '{$quiz->id}' AND preview = '0'", 'COUNT(DISTINCT userid)'); $a->studentstring = $course->students; notify(get_string('numattempts', 'quiz', $a)); } } $context = get_context_instance(CONTEXT_MODULE, $cm->id); /// find out current groups mode if ($groupmode = groupmode($course, $cm)) { // Groups are being used if (!$download) { $currentgroup = setup_and_print_groups($course, $groupmode, $reporturlwithoptions); } else { $currentgroup = get_and_set_current_group($course, $groupmode); } } else { $currentgroup = get_and_set_current_group($course, $groupmode); } $hasfeedback = quiz_has_feedback($quiz->id) && $quiz->grade > 1.0E-7 && $quiz->sumgrades > 1.0E-7; if ($pagesize < 1) { $pagesize = 10; } // Now check if asked download of data if ($download) { $filename = clean_filename("{$course->shortname} " . format_string($quiz->name, true)); $sort = ''; } // Define table columns $tablecolumns = array('checkbox', 'picture', 'fullname', 'timestart', 'timefinish', 'duration'); $tableheaders = array(NULL, '', get_string('name'), get_string('startedon', 'quiz'), get_string('timecompleted', 'quiz'), get_string('attemptduration', 'quiz')); if ($quiz->grade and $quiz->sumgrades) { $tablecolumns[] = 'sumgrades'; $tableheaders[] = get_string('grade', 'quiz') . '/' . $quiz->grade; } if ($detailedmarks) { // we want to display marks for all questions // Start by getting all questions $questionlist = quiz_questions_in_quiz($quiz->questions); $questionids = explode(',', $questionlist); $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 = '{$quiz->id}' AND q.id = i.question" . " AND q.id IN ({$questionlist})"; if (!($questions = get_records_sql($sql))) { error('No questions found'); } $number = 1; foreach ($questionids as $key => $id) { if ($questions[$id]->length) { // Only print questions of non-zero length $tablecolumns[] = '$' . $id; $tableheaders[] = '#' . $number; $questions[$id]->number = $number; $number += $questions[$id]->length; } else { // get rid of zero length questions unset($questions[$id]); unset($questionids[$key]); } } } if ($hasfeedback) { $tablecolumns[] = 'feedbacktext'; $tableheaders[] = get_string('feedback', 'quiz'); } if (!$download) { // Set up the table $table = new flexible_table('mod-quiz-report-overview-report'); $table->define_columns($tablecolumns); $table->define_headers($tableheaders); $table->define_baseurl($reporturlwithoptions); $table->sortable(true); $table->collapsible(true); $table->column_suppress('picture'); $table->column_suppress('fullname'); $table->column_class('picture', 'picture'); $table->set_attribute('cellspacing', '0'); $table->set_attribute('id', 'attempts'); $table->set_attribute('class', 'generaltable generalbox'); // Start working -- this is necessary as soon as the niceties are over $table->setup(); } else { if ($download == 'ODS') { require_once "{$CFG->libdir}/odslib.class.php"; $filename .= ".ods"; // Creating a workbook $workbook = new MoodleODSWorkbook("-"); // Sending HTTP headers $workbook->send($filename); // Creating the first worksheet $sheettitle = get_string('reportoverview', 'quiz'); $myxls =& $workbook->add_worksheet($sheettitle); // format types $format =& $workbook->add_format(); $format->set_bold(0); $formatbc =& $workbook->add_format(); $formatbc->set_bold(1); $formatbc->set_align('center'); $formatb =& $workbook->add_format(); $formatb->set_bold(1); $formaty =& $workbook->add_format(); $formaty->set_bg_color('yellow'); $formatc =& $workbook->add_format(); $formatc->set_align('center'); $formatr =& $workbook->add_format(); $formatr->set_bold(1); $formatr->set_color('red'); $formatr->set_align('center'); $formatg =& $workbook->add_format(); $formatg->set_bold(1); $formatg->set_color('green'); $formatg->set_align('center'); // Here starts workshhet headers $headers = array(get_string('name'), get_string('startedon', 'quiz'), get_string('timecompleted', 'quiz'), get_string('attemptduration', 'quiz')); if ($quiz->grade and $quiz->sumgrades) { $headers[] = get_string('grade', 'quiz') . '/' . $quiz->grade; } if ($detailedmarks) { foreach ($questionids as $id) { $headers[] = '#' . $questions[$id]->number; } } if ($hasfeedback) { $headers[] = get_string('feedback', 'quiz'); } $colnum = 0; foreach ($headers as $item) { $myxls->write(0, $colnum, $item, $formatbc); $colnum++; } $rownum = 1; } else { if ($download == 'Excel') { require_once "{$CFG->libdir}/excellib.class.php"; $filename .= ".xls"; // Creating a workbook $workbook = new MoodleExcelWorkbook("-"); // Sending HTTP headers $workbook->send($filename); // Creating the first worksheet $sheettitle = get_string('reportoverview', 'quiz'); $myxls =& $workbook->add_worksheet($sheettitle); // format types $format =& $workbook->add_format(); $format->set_bold(0); $formatbc =& $workbook->add_format(); $formatbc->set_bold(1); $formatbc->set_align('center'); $formatb =& $workbook->add_format(); $formatb->set_bold(1); $formaty =& $workbook->add_format(); $formaty->set_bg_color('yellow'); $formatc =& $workbook->add_format(); $formatc->set_align('center'); $formatr =& $workbook->add_format(); $formatr->set_bold(1); $formatr->set_color('red'); $formatr->set_align('center'); $formatg =& $workbook->add_format(); $formatg->set_bold(1); $formatg->set_color('green'); $formatg->set_align('center'); // Here starts workshhet headers $headers = array(get_string('name'), get_string('startedon', 'quiz'), get_string('timecompleted', 'quiz'), get_string('attemptduration', 'quiz')); if ($quiz->grade and $quiz->sumgrades) { $headers[] = get_string('grade', 'quiz') . '/' . $quiz->grade; } if ($detailedmarks) { foreach ($questionids as $id) { $headers[] = '#' . $questions[$id]->number; } } if ($hasfeedback) { $headers[] = get_string('feedback', 'quiz'); } $colnum = 0; foreach ($headers as $item) { $myxls->write(0, $colnum, $item, $formatbc); $colnum++; } $rownum = 1; } else { if ($download == 'CSV') { $filename .= ".txt"; header("Content-Type: application/download\n"); header("Content-Disposition: attachment; filename=\"{$filename}\""); header("Expires: 0"); header("Cache-Control: must-revalidate,post-check=0,pre-check=0"); header("Pragma: public"); $headers = get_string('name') . "\t" . get_string('startedon', 'quiz') . "\t" . get_string('timecompleted', 'quiz') . "\t" . get_string('attemptduration', 'quiz'); if ($quiz->grade and $quiz->sumgrades) { $headers .= "\t" . get_string('grade', 'quiz') . "/" . $quiz->grade; } if ($detailedmarks) { foreach ($questionids as $id) { $headers .= "\t#" . $questions[$id]->number; } } if ($hasfeedback) { $headers .= "\t" . get_string('feedback', 'quiz'); } echo $headers . " \n"; } } } } $contextlists = get_related_contexts_string(get_context_instance(CONTEXT_COURSE, $course->id)); // Construct the SQL $select = 'SELECT ' . sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')) . ' AS uniqueid, ' . 'qa.uniqueid as attemptuniqueid, qa.id AS attempt, u.id AS userid, u.firstname, u.lastname, u.picture, ' . 'qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration '; if ($course->id != SITEID) { // this is too complicated, so just do it for each of the four cases. if (!empty($currentgroup) && empty($noattempts)) { // we want a particular group and we only want to see students WITH attempts. // So join on groups_members and do an inner join on attempts. $from = 'FROM ' . $CFG->prefix . 'user u JOIN ' . $CFG->prefix . 'role_assignments ra ON ra.userid = u.id ' . groups_members_join_sql() . 'JOIN ' . $CFG->prefix . 'quiz_attempts qa ON u.id = qa.userid AND qa.quiz = ' . $quiz->id; $where = ' WHERE ra.contextid ' . $contextlists . ' AND ' . groups_members_where_sql($currentgroup) . ' AND qa.preview = 0'; } else { if (!empty($currentgroup) && !empty($noattempts)) { // We want a particular group and we want to do something funky with attempts // So join on groups_members and left join on attempts... $from = 'FROM ' . $CFG->prefix . 'user u JOIN ' . $CFG->prefix . 'role_assignments ra ON ra.userid = u.id ' . groups_members_join_sql() . 'LEFT JOIN ' . $CFG->prefix . 'quiz_attempts qa ON u.id = qa.userid AND qa.quiz = ' . $quiz->id; $where = ' WHERE ra.contextid ' . $contextlists . ' AND ' . groups_members_where_sql($currentgroup); if ($noattempts == 1) { // noattempts = 1 means only no attempts, so make the left join ask for only records where the right is null (no attempts) $where .= ' AND qa.userid IS NULL'; // show ONLY no attempts; } else { // We are including attempts, so exclude previews. $where .= ' AND qa.preview = 0'; } } else { if (empty($currentgroup)) { // We don't care about group, and we to do something funky with attempts // So do a left join on attempts $from = 'FROM ' . $CFG->prefix . 'user u JOIN ' . $CFG->prefix . 'role_assignments ra ON ra.userid = u.id LEFT JOIN ' . $CFG->prefix . 'quiz_attempts qa ON u.id = qa.userid AND qa.quiz = ' . $quiz->id; $where = " WHERE ra.contextid {$contextlists}"; if (empty($noattempts)) { $where .= ' AND qa.userid IS NOT NULL AND qa.preview = 0'; // show ONLY students with attempts; } else { if ($noattempts == 1) { // noattempts = 1 means only no attempts, so make the left join ask for only records where the right is null (no attempts) $where .= ' AND qa.userid IS NULL'; // show ONLY students without attempts; } else { if ($noattempts == 3) { // we want all attempts $from = 'FROM ' . $CFG->prefix . 'user u JOIN ' . $CFG->prefix . 'quiz_attempts qa ON u.id = qa.userid '; $where = ' WHERE qa.quiz = ' . $quiz->id . ' AND qa.preview = 0'; } } } // noattempts = 2 means we want all students, with or without attempts } } } $countsql = 'SELECT COUNT(DISTINCT(' . sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')) . ')) ' . $from . $where; } else { if (empty($noattempts)) { $from = 'FROM ' . $CFG->prefix . 'user u JOIN ' . $CFG->prefix . 'quiz_attempts qa ON u.id = qa.userid '; $where = ' WHERE qa.quiz = ' . $quiz->id . ' AND qa.preview = 0'; $countsql = 'SELECT COUNT(DISTINCT(' . sql_concat('u.id', '\'#\'', $db->IfNull('qa.attempt', '0')) . ')) ' . $from . $where; } } if (!$download) { // Add extra limits due to initials bar if ($table->get_sql_where()) { $where .= ' AND ' . $table->get_sql_where(); } // Count the records NOW, before funky question grade sorting messes up $from if (!empty($countsql)) { $totalinitials = count_records_sql($countsql); if ($table->get_sql_where()) { $countsql .= ' AND ' . $table->get_sql_where(); } $total = count_records_sql($countsql); } // Add extra limits due to sorting by question grade if ($sort = $table->get_sql_sort()) { $sortparts = explode(',', $sort); $newsort = array(); $questionsort = false; foreach ($sortparts as $sortpart) { $sortpart = trim($sortpart); if (substr($sortpart, 0, 1) == '$') { if (!$questionsort) { $qid = intval(substr($sortpart, 1)); $select .= ', grade '; $from .= ' LEFT JOIN ' . $CFG->prefix . 'question_sessions qns ON qns.attemptid = qa.id ' . 'LEFT JOIN ' . $CFG->prefix . 'question_states qs ON qs.id = qns.newgraded '; $where .= ' AND (' . sql_isnull('qns.questionid') . ' OR qns.questionid = ' . $qid . ')'; $newsort[] = 'grade ' . (strpos($sortpart, 'ASC') ? 'ASC' : 'DESC'); $questionsort = true; } } else { $newsort[] = $sortpart; } } // Reconstruct the sort string $sort = ' ORDER BY ' . implode(', ', $newsort); } // Fix some wired sorting if (empty($sort)) { $sort = ' ORDER BY uniqueid'; } $table->pagesize($pagesize, $total); } // If there is feedback, include it in the query. if ($hasfeedback) { $factor = $quiz->grade / $quiz->sumgrades; $select .= ', qf.feedbacktext '; $from .= " LEFT JOIN {$CFG->prefix}quiz_feedback qf ON " . "qf.quizid = {$quiz->id} AND qf.mingrade <= qa.sumgrades * {$factor} AND qa.sumgrades * {$factor} < qf.maxgrade"; } // Fetch the attempts if (!empty($from)) { // if we're in the site course and displaying no attempts, it makes no sense to do the query. if (!$download) { $attempts = get_records_sql($select . $from . $where . $sort, $table->get_page_start(), $table->get_page_size()); } else { $attempts = get_records_sql($select . $from . $where . $sort); } } else { $attempts = array(); } // Build table rows if (!$download) { $table->initialbars($totalinitials > 20); } if (!empty($attempts) || !empty($noattempts)) { if ($attempts) { foreach ($attempts as $attempt) { $picture = print_user_picture($attempt->userid, $course->id, $attempt->picture, false, true); // uncomment the commented lines below if you are choosing to show unenrolled users and // have uncommented the corresponding lines earlier in this script //if (in_array($attempt->userid, $unenrolledusers)) { // $userlink = '<a class="dimmed" href="'.$CFG->wwwroot.'/user/view.php?id='.$attempt->userid.'&course='.$course->id.'">'.fullname($attempt).'</a>'; //} //else { $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $attempt->userid . '&course=' . $course->id . '">' . fullname($attempt) . '</a>'; //} if (!$download) { $row = array('<input type="checkbox" name="attemptid[]" value="' . $attempt->attempt . '" />', $picture, $userlink, empty($attempt->attempt) ? '-' : '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . userdate($attempt->timestart, $strtimeformat) . '</a>', empty($attempt->timefinish) ? '-' : '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . userdate($attempt->timefinish, $strtimeformat) . '</a>', empty($attempt->attempt) ? '-' : (empty($attempt->timefinish) ? get_string('unfinished', 'quiz') : format_time($attempt->duration))); } else { $row = array(fullname($attempt), empty($attempt->attempt) ? '-' : userdate($attempt->timestart, $strtimeformat), empty($attempt->timefinish) ? '-' : userdate($attempt->timefinish, $strtimeformat), empty($attempt->attempt) ? '-' : (empty($attempt->timefinish) ? get_string('unfinished', 'quiz') : format_time($attempt->duration))); } if ($quiz->grade and $quiz->sumgrades) { if (!$download) { $row[] = $attempt->sumgrades === NULL ? '-' : '<a href="review.php?q=' . $quiz->id . '&attempt=' . $attempt->attempt . '">' . round($attempt->sumgrades / $quiz->sumgrades * $quiz->grade, $quiz->decimalpoints) . '</a>'; } else { $row[] = $attempt->sumgrades === NULL ? '-' : round($attempt->sumgrades / $quiz->sumgrades * $quiz->grade, $quiz->decimalpoints); } } if ($detailedmarks) { if (empty($attempt->attempt)) { foreach ($questionids as $questionid) { $row[] = '-'; } } else { foreach ($questionids as $questionid) { if ($gradedstateid = get_field('question_sessions', 'newgraded', 'attemptid', $attempt->attemptuniqueid, 'questionid', $questionid)) { $grade = round(get_field('question_states', 'grade', 'id', $gradedstateid), $quiz->decimalpoints); } else { $grade = '--'; } if (!$download) { $row[] = link_to_popup_window('/mod/quiz/reviewquestion.php?state=' . $gradedstateid . '&number=' . $questions[$questionid]->number, 'reviewquestion', $grade, 450, 650, $strreviewquestion, 'none', true); } else { $row[] = $grade; } } } } if ($hasfeedback) { if ($attempt->timefinish) { if (empty($attempt->feedbacktext)) { $attempt->feedbacktext = ''; } $row[] = format_text($attempt->feedbacktext, FORMAT_MOODLE, $nocleanformatoptions); } else { $row[] = '-'; } } if (!$download) { $table->add_data($row); } else { if ($download == 'Excel' or $download == 'ODS') { $colnum = 0; foreach ($row as $item) { $myxls->write($rownum, $colnum, $item, $format); $colnum++; } $rownum++; } else { if ($download == 'CSV') { $text = implode("\t", $row); echo $text . " \n"; } } } } } if (!$download) { // Start form echo '<div id="tablecontainer">'; echo '<form id="attemptsform" method="post" action="' . $reporturlwithoptions . '" onsubmit="var menu = document.getElementById(\'menuaction\'); return (menu.options[menu.selectedIndex].value == \'delete\' ? confirm(\'' . $strreallydel . '\') : true);">'; echo '<div>'; // Print table $table->print_html(); // Prepare list of available options. $options = array(); if (has_capability('mod/quiz:deleteattempts', $context)) { $options['delete'] = get_string('delete'); } // Print "Select all" etc. if (!empty($attempts) && !empty($options)) { echo '<table id="commands">'; echo '<tr><td>'; echo '<a href="javascript:select_all_in(\'DIV\',null,\'tablecontainer\');">' . get_string('selectall', 'quiz') . '</a> / '; echo '<a href="javascript:deselect_all_in(\'DIV\',null,\'tablecontainer\');">' . get_string('selectnone', 'quiz') . '</a> '; echo ' '; echo choose_from_menu($options, 'action', '', get_string('withselected', 'quiz'), 'if(this.selectedIndex > 0) submitFormById(\'attemptsform\');', '', true); echo '<noscript id="noscriptmenuaction" style="display: inline;"><div>'; echo '<input type="submit" value="' . get_string('go') . '" /></div></noscript>'; echo '<script type="text/javascript">' . "\n<!--\n" . 'document.getElementById("noscriptmenuaction").style.display = "none";' . "\n-->\n" . '</script>'; echo '</td></tr></table>'; } // Close form echo '</div>'; echo '</form></div>'; if (!empty($attempts)) { echo '<table class="boxaligncenter"><tr>'; $options = array(); $options["id"] = $cm->id; $options["q"] = $quiz->id; $options['sesskey'] = sesskey(); $options["noheader"] = "yes"; $options['noattempts'] = $noattempts; $options['detailedmarks'] = $detailedmarks; echo '<td>'; $options["download"] = "ODS"; print_single_button($reporturl, $options, get_string("downloadods")); echo "</td>\n"; echo '<td>'; $options["download"] = "Excel"; print_single_button($reporturl, $options, get_string("downloadexcel")); echo "</td>\n"; echo '<td>'; $options["download"] = "CSV"; print_single_button($reporturl, $options, get_string("downloadtext")); echo "</td>\n"; echo "<td>"; helpbutton('overviewdownload', get_string('overviewdownload', 'quiz_overview'), 'quiz'); echo "</td>\n"; echo '</tr></table>'; } } else { if ($download == 'Excel' or $download == 'ODS') { $workbook->close(); exit; } else { if ($download == 'CSV') { exit; } } } } else { if (!$download) { $table->print_html(); } } // Print display options echo '<div class="controls">'; echo '<form id="options" action="' . $reporturl . '" method="get">'; echo '<div>'; echo '<p>' . get_string('displayoptions', 'quiz') . ': </p>'; echo '<input type="hidden" name="id" value="' . $cm->id . '" />'; echo '<input type="hidden" name="q" value="' . $quiz->id . '" />'; echo '<input type="hidden" name="noattempts" value="0" />'; echo '<input type="hidden" name="detailedmarks" value="0" />'; echo '<table id="overview-options" class="boxaligncenter">'; echo '<tr align="left">'; echo '<td><label for="pagesize">' . get_string('pagesize', 'quiz') . '</label></td>'; echo '<td><input type="text" id="pagesize" name="pagesize" size="3" value="' . $pagesize . '" /></td>'; echo '</tr>'; echo '<tr align="left">'; echo '<td colspan="2">'; $options = array(0 => get_string('attemptsonly', 'quiz_overview', $course->students)); if ($course->id != SITEID) { $options[1] = get_string('noattemptsonly', 'quiz_overview', $course->students); $options[2] = get_string('allstudents', 'quiz_overview', $course->students); $options[3] = get_string('allattempts', 'quiz_overview'); } choose_from_menu($options, 'noattempts', $noattempts, ''); echo '</td></tr>'; echo '<tr align="left">'; echo '<td colspan="2"><input type="checkbox" id="checkdetailedmarks" name="detailedmarks" ' . ($detailedmarks ? 'checked="checked" ' : '') . 'value="1" /> <label for="checkdetailedmarks">' . get_string('showdetailedmarks', 'quiz') . '</label> '; echo '</td></tr>'; echo '<tr><td colspan="2" align="center">'; echo '<input type="submit" value="' . get_string('go') . '" />'; echo '</td></tr></table>'; echo '</div>'; echo '</form>'; echo '</div>'; echo "\n"; return true; }
} else { $success = false; } } if (!$success) { print_error('errorprocessingresponses', 'question', $attemptobj->attempt_url(0, $page)); } /// Log the end of this attempt. add_to_log($attemptobj->get_courseid(), 'quiz', 'close attempt', 'review.php?attempt=' . $attemptobj->get_attemptid(), $attemptobj->get_quizid(), $attemptobj->get_cmid()); /// Update the quiz attempt record. $attempt->timemodified = $timenow; $attempt->timefinish = $timenow; $DB->update_record('quiz_attempts', $attempt); if (!$attempt->preview) { /// Record this user's best grade (if this is not a preview). quiz_save_best_grade($attemptobj->get_quiz()); /// Send any notification emails (if this is not a preview). $attemptobj->quiz_send_notification_emails(); } /// Clear the password check flag in the session. $accessmanager = $attemptobj->get_access_manager($timenow); $accessmanager->clear_password_access(); /// Trigger event $eventdata = new stdClass(); $eventdata->component = 'mod_quiz'; $eventdata->course = $attemptobj->get_courseid(); $eventdata->quiz = $attemptobj->get_quizid(); $eventdata->cm = $attemptobj->get_cmid(); $eventdata->user = $USER; $eventdata->attempt = $attemptobj->get_attemptid(); events_trigger('quiz_attempt_processed', $eventdata);
/** * Process a manual comment for a question in this attempt. * @param $questionid * @param integer $questionid the question id * @param string $comment the new comment from the teacher. * @param mixed $grade the grade the teacher assigned, or '' to not change the grade. * @return mixed true on success, a string error message if a problem is detected * (for example score out of range). */ public function process_comment($questionid, $comment, $commentformat, $grade) { // I am not sure it is a good idea to have update methods here - this // class is only about getting data out of the question engine, and // helping to display it, apart from this. $this->ensure_question_loaded($questionid); $this->ensure_state_loaded($questionid); $state = $this->states[$questionid]; $error = question_process_comment($this->questions[$questionid], $state, $this->attempt, $comment, $commentformat, $grade); // If the state was update (successfully), save the changes. if (!is_string($error) && $state->changed) { if (!save_question_session($this->questions[$questionid], $state)) { $error = get_string('errorudpatingquestionsession', 'quiz'); } if (!quiz_save_best_grade($this->quiz, $this->attempt->userid)) { $error = get_string('errorudpatingbestgrade', 'quiz'); } } return $error; }
public function process_comment($questionid, $comment, $grade) { $this->ensure_question_loaded($questionid); $this->ensure_state_loaded($questionid); $state = $this->states[$questionid]; $error = question_process_comment($this->questions[$questionid], $state, $this->attempt, $comment, $grade); // If the state was update (successfully), save the changes. if (!is_string($error) && $state->changed) { if (!save_question_session($this->questions[$questionid], $state)) { $error = get_string('errorudpatingquestionsession', 'quiz'); } if (!quiz_save_best_grade($this->quiz, $this->attempt->userid)) { $error = get_string('errorudpatingbestgrade', 'quiz'); } } return $error; }
function evaluate_quiz($acode, $jobid, $newattempt, $blended) { global $USER; global $CFG; mtrace("Evaluation QUIZ Processing..." . "<BR><BR>"); try { print "New Attempt is: " . $newattempt . "<BR/>"; $detected_userid = find_userid($acode, $jobid); if ($detected_userid == null or $detected_userid == '') { throw new EvaluationError(get_string('ErrorUserIDEmpty', 'blended'), EvaluationError::USERID_IS_EMPTY); } $user_reg = blended_get_user($detected_userid, $blended); if ($user_reg == null) { throw new EvaluationError(get_string('ErrorUserNotInCourse', 'blended'), EvaluationError::USER_NOT_IN_THIS_COURSE); } $userid = $user_reg->id; mtrace('Obtained USERID value: ' . $userid . " OK. <BR/>"); $quiz = get_quiz($acode); $attempts = quiz_get_user_attempts($quiz->id, $userid, 'all', true); mtrace("Searching quiz... Success." . "<BR/>"); $uniqueid = get_uniqueid($acode); mtrace('Obtained uniqueid: OK. <BR/>'); $timestamp = get_timestamp($acode); mtrace('Obtained timestamp: OK. <BR/>'); if (!get_record('quiz_attempts', 'uniqueid', $uniqueid)) { $newattempt = true; } else { $newattempt = false; mtrace("User {$userid} had opened this attempt already."); } $attemptnumber = 1; if ($newattempt == false) { mtrace('Obtaining user attempt...<BR/>'); set_attempt_unfinished($uniqueid); $attempt = quiz_get_user_attempt_unfinished($quiz->id, $userid); } elseif ($newattempt == true) { mtrace('Creating new attempt...<BR/>'); $attempt = create_new_attempt($quiz, $attemptnumber, $userid, $acode, $uniqueid, $timestamp); // Save the attempt if (!insert_record('quiz_attempts', $attempt)) { throw new EvaluationError(get_string('ErrorCouldNotCreateAttempt', 'blended'), EvaluationError::CREATE_QUIZ_ATTEMPT_ERROR); } // Actualizamos el estado de las imágenes para indicar que ya está creado un nuevo attempt update_images_status($acode, $jobid); } update_question_attempts($uniqueid); // /* mtrace('<BR>Getting questions and question options... '); $questions = get_questions($attempt, $quiz); if (!get_question_options($questions)) { error('Could not load question options'); } mtrace('Success! <BR>'); // print ("<BR>He obtenido questions: "); //print_object($questions); $lastattemptid = false; // if ($attempt->attempt > 1 and $quiz->attemptonlast and !$attempt->preview) { // Find the previous attempt // if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt-1)) { // error('Could not find previous attempt to build on'); // } //} //print ('He obtenido lastattemptid'); mtrace('Getting question states... '); if (!($states = get_question_states($questions, $quiz, $attempt, $lastattemptid))) { error('Could not restore question sessions'); } mtrace('Success! <BR>'); mtrace('Getting responses... <BR>'); $responses = get_responses($acode, $jobid, $attempt); //print('Estas son las responses:'); //print_object($responses); //$timestamp=time(); $event = 8; $actions = question_extract_responses($questions, $responses, $event); $questionids = get_questionids($acode); // print $questionids; $questionidarray = explode(',', $questionids); $success = true; mtrace('<BR> Processing responses and saving session... '); foreach ($questionidarray as $i) { if (!isset($actions[$i])) { $actions[$i]->responses = array('' => ''); $actions[$i]->event = QUESTION_EVENTOPEN; } $actions[$i]->timestamp = $timestamp; if (question_process_responses($questions[$i], $states[$i], $actions[$i], $quiz, $attempt)) { save_question_session($questions[$i], $states[$i]); } else { $success = false; } } mtrace('Success! <BR>'); // Set the attempt to be finished $timestamp = time(); //$attempt->timefinish = $timestamp; // Update the quiz attempt and the overall grade for the quiz mtrace('<BR> Finishing the attempt... '); // print_object ($attempt); if (set_field('quiz_attempts', 'timefinish', $timestamp, 'uniqueid', $uniqueid) == false) { throw new EvaluationError('Unable to finish the quiz attempt!', EvaluationError::FINISH_QUIZ_ATTEMPT_ERROR); } mtrace('Success! <BR>'); if ($attempt->attempt > 1 || $attempt->timefinish > 0 and !$attempt->preview) { mtrace('<BR> Saving quiz grade... '); quiz_save_best_grade($quiz, $userid); } mtrace('Success! <BR>'); // */ mtrace("Process Done. <BR><BR>"); mtrace("<center> Your quiz has been succesfully evaluated!! </center>"); } catch (EvaluationError $e) { throw $e; } return; }
/** * Delete a quiz attempt. * @param mixed $attempt an integer attempt id or an attempt object (row of the quiz_attempts table). * @param object $quiz the quiz object. */ function quiz_delete_attempt($attempt, $quiz) { if (is_numeric($attempt)) { if (!($attempt = get_record('quiz_attempts', 'id', $attempt))) { return; } } if ($attempt->quiz != $quiz->id) { debugging("Trying to delete attempt {$attempt->id} which belongs to quiz {$attempt->quiz} " . "but was passed quiz {$quiz->id}."); return; } delete_records('quiz_attempts', 'id', $attempt->id); delete_attempt($attempt->uniqueid); // Search quiz_attempts for other instances by this user. // If none, then delete record for this quiz, this user from quiz_grades // else recalculate best grade $userid = $attempt->userid; if (!record_exists('quiz_attempts', 'userid', $userid, 'quiz', $quiz->id)) { delete_records('quiz_grades', 'userid', $userid, 'quiz', $quiz->id); } else { quiz_save_best_grade($quiz, $userid); } quiz_update_grades($quiz, $userid); }
/** * Displays the report. */ function display($quiz, $cm, $course) { global $CFG, $SESSION, $USER, $db, $QTYPES; $action = optional_param('action', 'viewquestions', PARAM_ALPHA); $questionid = optional_param('questionid', 0, PARAM_INT); $this->print_header_and_tabs($cm, $course, $quiz, $reportmode = "grading"); // Check permissions $context = get_context_instance(CONTEXT_MODULE, $cm->id); if (!has_capability('mod/quiz:grade', $context)) { notify(get_string('gradingnotallowed', 'quiz_grading')); return true; } if (!empty($questionid)) { if (!($question = get_record('question', 'id', $questionid))) { error("Question with id {$questionid} not found"); } $question->maxgrade = get_field('quiz_question_instances', 'grade', 'quiz', $quiz->id, 'question', $question->id); // Some of the questions code is optimised to work with several questions // at once so it wants the question to be in an array. The array key // must be the question id. $key = $question->id; $questions[$key] =& $question; // We need to add additional questiontype specific information to // the question objects. if (!get_question_options($questions)) { error("Unable to load questiontype specific question information"); } // This will have extended the question object so that it now holds // all the information about the questions that may be needed later. } add_to_log($course->id, "quiz", "manualgrading", "report.php?mode=grading&q={$quiz->id}", "{$quiz->id}", "{$cm->id}"); echo '<div id="overDiv" style="position:absolute; visibility:hidden; z-index:1000;"></div>'; // for overlib if ($data = data_submitted()) { // post data submitted, process it confirm_sesskey(); // now go through all of the responses and save them. foreach ($data->manualgrades as $uniqueid => $response) { // get our attempt if (!($attempt = get_record('quiz_attempts', 'uniqueid', $uniqueid))) { error('No such attempt ID exists'); } // Load the state for this attempt (The questions array was created earlier) $states = get_question_states($questions, $quiz, $attempt); // The $states array is indexed by question id but because we are dealing // with only one question there is only one entry in this array $state =& $states[$question->id]; // the following will update the state and attempt question_process_comment($question, $state, $attempt, $response['comment'], $response['grade']); // If the state has changed save it and update the quiz grade if ($state->changed) { save_question_session($question, $state); quiz_save_best_grade($quiz, $attempt->userid); } } notify(get_string('changessaved', 'quiz'), 'notifysuccess'); } // our 3 different views // the first one displays all of the manually graded questions in the quiz // with the number of ungraded attempts for each question // the second view displays the users who have answered the essay question // and all of their attempts at answering the question // the third prints the question with a comment // and grade form underneath it switch ($action) { case 'viewquestions': $this->view_questions($quiz); break; case 'viewquestion': $this->view_question($quiz, $question); break; case 'grade': $this->print_questions_and_form($quiz, $question); break; } return true; }
public function process_finish($timestamp, $processsubmitted) { global $DB; $transaction = $DB->start_delegated_transaction(); if ($processsubmitted) { $this->quba->process_all_actions($timestamp); } $this->quba->finish_all_questions($timestamp); question_engine::save_questions_usage_by_activity($this->quba); $this->attempt->timemodified = $timestamp; $this->attempt->timefinish = $timestamp; $this->attempt->sumgrades = $this->quba->get_total_mark(); $this->attempt->state = self::FINISHED; $this->attempt->timecheckstate = null; $DB->update_record('quiz_attempts', $this->attempt); if (!$this->is_preview()) { quiz_save_best_grade($this->get_quiz(), $this->attempt->userid); // Trigger event. $this->fire_state_transition_event('\\mod_quiz\\event\\attempt_submitted', $timestamp); // Tell any access rules that care that the attempt is over. $this->get_access_manager($timestamp)->current_attempt_finished(); } $transaction->allow_commit(); }
public function finish_attempt($timestamp) { global $DB; $this->quba->process_all_actions($timestamp); $this->quba->finish_all_questions($timestamp); question_engine::save_questions_usage_by_activity($this->quba); $this->attempt->timemodified = $timestamp; $this->attempt->timefinish = $timestamp; $this->attempt->sumgrades = $this->quba->get_total_mark(); $DB->update_record('quiz_attempts', $this->attempt); if (!$this->is_preview()) { quiz_save_best_grade($this->get_quiz()); $this->quiz_send_notification_emails(); } }