$string['allstudents'] = 'Show all $a';
$string['attemptsonly'] = 'Show $a with attempts only';
$string['attemptsprepage'] = 'Attempts shown per page';
+$string['attemptprogress'] = 'Attempt $a->done of $a->todo';
$string['deleteselected'] = 'Delete selected attempts';
+$string['done'] = 'Done';
+$string['err_failedtorecalculateattemptgrades'] = 'Failed to recalculate attempt grades';
+$string['err_failedtodeleteregrades'] = 'Failed to delete calculated attempt grades';
$string['highlightinggraded'] = 'The user attempt that contributes to final grade is highlighted.';
+$string['needed'] = 'Needed';
+$string['noattemptstoregrade'] = 'No attempts need regrading';
$string['noattemptsonly'] = 'Show / download $a with no attempts only';
+$string['nogradepermission'] = 'You don\'t have permission to grade this quiz.';
$string['onlyoneattemptallowed'] = 'Only one attempt per user allowed on this quiz.';
$string['optallattempts'] = 'all attempts';
$string['optallstudents'] = 'all $a who have or have not attempted the quiz';
$string['optattemptsonly'] = '$a who have attempted the quiz';
$string['optnoattemptsonly'] = '$a who have not attempted the quiz';
-$string['optonlygradedattempts'] = 'only the attempt that is graded for each user ($a)';
+$string['optonlygradedattempts'] = 'that are graded for each user ($a)';
+$string['optonlyregradedattempts'] = 'that have been regraded / are marked as needing regrading';
$string['overview'] = 'Grades';
$string['overviewdownload'] = 'Overview download';
$string['overviewdownload'] = 'Overview download';
$string['preferencespage'] = 'Preferences just for this page';
$string['preferencessave'] = 'Save preferences';
$string['preferencesuser'] = 'Your preferences for this report';
+$string['qprogress'] = 'Question $a->done of $a->todo';
+$string['regrade'] = 'Regrade';
+$string['regradeheader'] = 'Regrading';
+$string['regradeall'] = 'Regrade all';
+$string['regradeallgroup'] = 'Full regrade for group \'$a->groupname\'';
+$string['regradealldry'] = 'Dryrun a full regrade';
+$string['regradealldrydo'] = 'Regrade attempts marked as needing regrading ($a)';
+$string['regradealldrydogroup'] = 'Regrade attempts ($a->countregradeneeded) marked as needing regrading in group \'$a->groupname\'';
+$string['regradealldrygroup'] = 'Dryrun a full regrade for group \'$a->groupname\'';
+$string['regradeselected'] = 'Regrade selected attempts';
$string['show'] = 'Show / download';
+$string['showattempts'] = 'Only show / download attempts';
$string['showinggraded'] = 'Showing only the attempt graded for each user.';
$string['showinggradedandungraded'] = 'Showing graded and ungraded attempts for each user. The one attempt for each user that is graded is highlighted. The grading method for this quiz is $a.';
$string['showdetailedmarks'] = 'Show / download marks for each question';
* @param object $attempt The attempt, in which the question needs to be regraded.
* @param object $cmoptions
* @param boolean $verbose Optional. Whether to print progress information or not.
+* @param boolean $dryrun Optional. Whether to make changes to grades records
+* or record that changes need to be made for a later regrade.
*/
-function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose=false) {
+function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose=false, $dryrun=false) {
global $DB;
// load all states for this question in this attempt, ordered in sequence
// proceeding.
if ($states[$j]->grade < 0) {
$states[$j]->grade = 0;
+ $changed = true;
} else if ($states[$j]->grade > $question->maxgrade) {
$states[$j]->grade = $question->maxgrade;
+ $changed = true;
+
}
- $error = question_process_comment($question, $replaystate, $attempt,
- $replaystate->manualcomment, $states[$j]->grade);
- if (is_string($error)) {
- notify($error);
+ if (!$dryrun){
+ $error = question_process_comment($question, $replaystate, $attempt,
+ $replaystate->manualcomment, $states[$j]->grade);
+ if (is_string($error)) {
+ notify($error);
+ }
+ } else {
+ $replaystate->grade = $states[$j]->grade;
}
} else {
-
// Reprocess (regrade) responses
if (!question_process_responses($question, $replaystate,
$action, $cmoptions, $attempt)) {
$verbose && notify("Couldn't regrade state #{$state->id}!");
}
+ // We need rounding here because grades in the DB get truncated
+ // e.g. 0.33333 != 0.3333333, but we want them to be equal here
+ if ((round((float)$replaystate->raw_grade, 5) != round((float)$states[$j]->raw_grade, 5))
+ or (round((float)$replaystate->penalty, 5) != round((float)$states[$j]->penalty, 5))
+ or (round((float)$replaystate->grade, 5) != round((float)$states[$j]->grade, 5))) {
+ $changed = true;
+ }
}
+
- // We need rounding here because grades in the DB get truncated
- // e.g. 0.33333 != 0.3333333, but we want them to be equal here
- if ((round((float)$replaystate->raw_grade, 5) != round((float)$states[$j]->raw_grade, 5))
- or (round((float)$replaystate->penalty, 5) != round((float)$states[$j]->penalty, 5))
- or (round((float)$replaystate->grade, 5) != round((float)$states[$j]->grade, 5))) {
- $changed = true;
- }
$replaystate->id = $states[$j]->id;
$replaystate->changed = true;
$replaystate->update = true; // This will ensure that the existing database entry is updated rather than a new one created
- save_question_session($question, $replaystate);
+ if (!$dryrun){
+ save_question_session($question, $replaystate);
+ }
}
if ($changed) {
- // TODO, call a method in quiz to do this, where 'quiz' comes from
- // the question_attempts table.
- $DB->update_record('quiz_attempts', $attempt);
+ if (!$dryrun){
+ // TODO, call a method in quiz to do this, where 'quiz' comes from
+ // the question_attempts table.
+ $DB->update_record('quiz_attempts', $attempt);
+ }
+ }
+ if ($changed){
+ $toinsert = new object();
+ $toinsert->oldgrade = round((float)$states[count($states)-1]->grade, 5);
+ $toinsert->newgrade = round((float)$replaystate->grade, 5);
+ $toinsert->attemptid = $attempt->uniqueid;
+ $toinsert->questionid = $question->id;
+ //the grade saved is the old grade if the new grade is saved
+ //it is the new grade if this is a dry run.
+ $toinsert->regraded = $dryrun?0:1;
+ $toinsert->timemodified = time();
+ $DB->insert_record('quiz_question_regrade', $toinsert);
+ return true;
+ } else {
+ return false;
}
-
- return $changed;
}
return false;
}
* @param string $status 'all', 'finished' or 'unfinished' to control
* @return an array of all the user's attempts at this quiz. Returns an empty array if there are none.
*/
-function quiz_get_user_attempts($quizid, $userid, $status = 'finished', $includepreviews = false) {
+function quiz_get_user_attempts($quizid, $userid=0, $status = 'finished', $includepreviews = false) {
global $DB;
$status_condition = array(
'all' => '',
if (!$includepreviews) {
$previewclause = ' AND preview = 0';
}
+ $params=array($quizid);
+ if ($userid){
+ $userclause = ' AND userid = ?';
+ $params[]=$userid;
+ } else {
+ $userclause = '';
+ }
if ($attempts = $DB->get_records_select('quiz_attempts',
- "quiz = ? AND userid = ?" . $previewclause . $status_condition[$status], array($quizid, $userid),
+ "quiz = ?" .$userclause. $previewclause . $status_condition[$status], $params,
'attempt ASC')) {
return $attempts;
} else {
*
* @param object $quiz The quiz for which the best grade is to be calculated and then saved.
* @param integer $userid The userid to calculate the grade for. Defaults to the current user.
+ * @param array $attempts The attempts of this user. Useful if you are
+ * looping through many users. Attempts can be fetched in one master query to
+ * avoid repeated querying.
* @return boolean Indicates success or failure.
*/
-function quiz_save_best_grade($quiz, $userid = null) {
+function quiz_save_best_grade($quiz, $userid = null, $attempts = array()) {
global $DB;
global $USER;
$userid = $USER->id;
}
- // Get all the attempts made by the user
- if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
- notify('Could not find any user attempts');
- return false;
+ if (!$attempts){
+ // Get all the attempts made by the user
+ if (!$attempts = quiz_get_user_attempts($quiz->id, $userid)) {
+ notify('Could not find any user attempts');
+ return false;
+ }
}
// Calculate the best grade
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<XMLDB PATH="mod/quiz/report/overview/db" VERSION="20080706" COMMENT="XMLDB file for Moodle mod/quiz/report/overview"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
+>
+ <TABLES>
+ <TABLE NAME="quiz_question_regrade" COMMENT="table to record which question attempts need regrading and the grade they will be regraded to.">
+ <FIELDS>
+ <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="questionid"/>
+ <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="attemptid"/>
+ <FIELD NAME="attemptid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="questionid" NEXT="grade"/>
+ <FIELD NAME="grade" TYPE="number" LENGTH="10" NOTNULL="false" UNSIGNED="false" SEQUENCE="false" ENUM="false" DECIMALS="5" PREVIOUS="attemptid" NEXT="regraded"/>
+ <FIELD NAME="regraded" TYPE="int" LENGTH="4" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" COMMENT="set to 0 if element has just been regraded. Set to 1 if element has been marked as needing regrading." PREVIOUS="grade" NEXT="timemodified"/>
+ <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="regraded"/>
+ </FIELDS>
+ <KEYS>
+ <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+ </KEYS>
+ </TABLE>
+ </TABLES>
+</XMLDB>
\ No newline at end of file
--- /dev/null
+<?php // $Id$
+
+function xmldb_quizreport_overview_upgrade($oldversion=0) {
+
+ global $CFG, $THEME, $DB;
+
+ $dbman = $DB->get_manager();
+
+ $result = true;
+
+//===== 1.9.0 upgrade line ======//
+
+ if ($result && $oldversion < 2008062700) {
+
+ /// Define table quiz_question_regrade to be created
+ $table = new xmldb_table('quiz_question_regrade');
+
+ /// Adding fields to table quiz_question_regrade
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null, null);
+ $table->add_field('questionid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('attemptid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('newgrade', XMLDB_TYPE_NUMBER, '10, 5', null, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('oldgrade', XMLDB_TYPE_NUMBER, '10, 5', null, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('regraded', XMLDB_TYPE_INTEGER, '4', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null, null, null);
+
+
+ /// Adding keys to table quiz_question_regrade
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+ /// Conditionally launch create table for quiz_question_regrade
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+ }
+
+
+ return $result;
+}
+
+?>
if (!$this->is_downloading()) {
if ($this->candelete) {
// Start form
- $strreallydel = addslashes_js(get_string('deleteattemptcheck','quiz'));
echo '<div id="tablecontainer">';
- echo '<form id="attemptsform" method="post" action="' . $this->reporturl->out(true) .
- '" onsubmit="confirm(\''.$strreallydel.'\');">';
+ echo '<form id="attemptsform" method="post" action="' . $this->reporturl->out(true) .'">';
echo '<div style="display: none;">';
echo $this->reporturl->hidden_params_out(array(), 0, $this->displayoptions);
echo '</div>';
if (!$this->is_downloading()) {
// Print "Select all" etc.
if ($this->candelete) {
- echo '<table id="commands">';
- echo '<tr><td>';
+ $strreallydel = addslashes_js(get_string('deleteattemptcheck','quiz'));
+ echo '<div id="commands">';
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 '<input type="submit" value="'.get_string('deleteselected', 'quiz_overview').'"/>';
- echo '</td></tr></table>';
+ echo '<input type="submit" name="regrade" value="'.get_string('regradeselected', 'quiz_overview').'"/>';
+ echo '<input type="submit" onclick="return confirm(\''.$strreallydel.'\');" name="delete" value="'.get_string('deleteselected', 'quiz_overview').'"/>';
+ echo '</div>';
// Close form
echo '</div>';
echo '</form></div>';
if ($attempt->timefinish) {
$grade = quiz_rescale_grade($attempt->sumgrades, $this->quiz);
if (!$this->is_downloading()) {
+ if (isset($this->regradedqs[$attempt->attemptuniqueid])){
+ $newsumgrade = 0;
+ $oldsumgrade = 0;
+ foreach ($this->questions as $question){
+ if (isset($this->regradedqs[$attempt->attemptuniqueid][$question->id])){
+ $newsumgrade += $this->regradedqs[$attempt->attemptuniqueid][$question->id]->newgrade;
+ $oldsumgrade += $this->regradedqs[$attempt->attemptuniqueid][$question->id]->oldgrade;
+ } else {
+ $newsumgrade += $this->gradedstatesbyattempt[$attempt->attemptuniqueid][$question->id]->grade;
+ $oldsumgrade += $this->gradedstatesbyattempt[$attempt->attemptuniqueid][$question->id]->grade;
+ }
+ }
+ $newsumgrade = quiz_rescale_grade($newsumgrade, $this->quiz);
+ $oldsumgrade = quiz_rescale_grade($oldsumgrade, $this->quiz);
+ $grade = "<del>$oldsumgrade</del><br />$newsumgrade";
+ }
$gradehtml = '<a href="review.php?q='.$this->quiz->id.'&attempt='.$attempt->attempt.'">'.$grade.'</a>';
if ($this->qmsubselect && $attempt->gradedattempt){
$gradehtml = '<div class="highlight">'.$gradehtml.'</div>';
}
}
function other_cols($colname, $attempt){
- static $gradedstatesbyattempt = null;
- if ($gradedstatesbyattempt === null){
- //get all the attempt ids we want to display on this page
- //or to export for download.
- if (!$this->is_downloading()) {
- $attemptids = array();
- foreach ($this->rawdata as $attempt){
- if ($attempt->attemptuniqueid > 0){
- $attemptids[] = $attempt->attemptuniqueid;
- }
- }
- $gradedstatesbyattempt = quiz_get_newgraded_states($attemptids, true, 'qs.id, qs.grade, qs.event, qs.question, qs.attempt');
- } else {
- $gradedstatesbyattempt = quiz_get_newgraded_states($this->sql, true, 'qs.id, qs.grade, qs.event, qs.question, qs.attempt');
- }
- }
+
if (preg_match('/^qsgrade([0-9]+)$/', $colname, $matches)){
$questionid = $matches[1];
$question = $this->questions[$questionid];
- $stateforqinattempt = $gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid];
- if (question_state_is_graded($stateforqinattempt)) {
- $grade = quiz_rescale_grade($stateforqinattempt->grade, $this->quiz);
+ if (isset($this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid])){
+ $stateforqinattempt = $this->gradedstatesbyattempt[$attempt->attemptuniqueid][$questionid];
} else {
- $grade = '--';
+ $stateforqinattempt = false;
}
- if (!$this->is_downloading()) {
- $grade = $grade.'/'.quiz_rescale_grade($question->grade, $this->quiz);
- return link_to_popup_window('/mod/quiz/reviewquestion.php?state='.
- $stateforqinattempt->id.'&number='.$question->number,
- 'reviewquestion', $grade, 450, 650, get_string('reviewresponse', 'quiz'),
- 'none', true);
+ if ($stateforqinattempt && question_state_is_graded($stateforqinattempt)) {
+ $grade = quiz_rescale_grade($stateforqinattempt->grade, $this->quiz);
+ if (!$this->is_downloading()) {
+ if (isset($this->regradedqs[$attempt->attemptuniqueid][$questionid])){
+ $gradefromdb = $grade;
+ $newgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->newgrade, $this->quiz);
+ $oldgrade = quiz_rescale_grade($this->regradedqs[$attempt->attemptuniqueid][$questionid]->oldgrade, $this->quiz);
+
+ $grade = '<del>'.$oldgrade.'</del><br />'.
+ $newgrade;
+ }
+ return link_to_popup_window('/mod/quiz/reviewquestion.php?state='.
+ $stateforqinattempt->id.'&number='.$question->number,
+ 'reviewquestion', $grade, 450, 650, get_string('reviewresponse', 'quiz'),
+ 'none', true);
+ } else {
+ return $grade;
+ }
} else {
- return $grade;
+ return '--';
}
} else {
return NULL;
}
}
-
+ function col_regraded($attempt){
+ if ($attempt->regraded == '') {
+ return '';
+ } else if ($attempt->regraded == 0) {
+ return get_string('needed', 'quiz_overview');
+ } else if ($attempt->regraded == 1) {
+ return get_string('done', 'quiz_overview');
+ }
+ }
function query_db($pagesize, $useinitialsbar=true){
// Add table joins so we can sort by question grade
// unfortunately can't join all tables necessary to fetch all grades
}
}
parent::query_db($pagesize, $useinitialsbar);
+ if ($this->detailedmarks){
+ //get all the attempt ids we want to display on this page
+ //or to export for download.
+ if (!$this->is_downloading()) {
+ $attemptids = array();
+ foreach ($this->rawdata as $attempt){
+ if ($attempt->attemptuniqueid > 0){
+ $attemptids[] = $attempt->attemptuniqueid;
+ }
+ }
+ $this->gradedstatesbyattempt = quiz_get_newgraded_states($attemptids, true, 'qs.id, qs.grade, qs.event, qs.question, qs.attempt');
+ $this->regradedqs = quiz_get_regraded_qs($attemptids);
+ } else {
+ $this->gradedstatesbyattempt = quiz_get_newgraded_states($this->sql, true, 'qs.id, qs.grade, qs.event, qs.question, qs.attempt');
+ $this->regradedqs = quiz_get_regraded_qs($this->sql);
+ }
+ }
}
}
?>
$options[QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO] = get_string('optnoattemptsonly', 'quiz_overview', $studentsstring);
}
$mform->addElement('select', 'attemptsmode', get_string('show', 'quiz_overview'), $options);
+
+ $showattemptsgrp = array();
if ($this->_customdata['qmsubselect']){
$gm = '<span class="highlight">'.quiz_get_grading_option_name($this->_customdata['quiz']->grademethod).'</span>';
- $mform->addElement('advcheckbox', 'qmfilter', get_string('show', 'quiz_overview'), get_string('optonlygradedattempts', 'quiz_overview', $gm), null, array(0,1));
+ $showattemptsgrp[] =& $mform->createElement('advcheckbox', 'qmfilter', get_string('showattempts', 'quiz_overview'), get_string('optonlygradedattempts', 'quiz_overview', $gm), null, array(0,1));
}
+
+ $showattemptsgrp[] =& $mform->createElement('advcheckbox', 'regradefilter', get_string('showattempts', 'quiz_overview'), get_string('optonlyregradedattempts', 'quiz_overview'), null, array(0,1));
+ $mform->addGroup($showattemptsgrp, null, get_string('showattempts', 'quiz_overview'), '<br />', false);
//-------------------------------------------------------------------------------
$mform->addElement('header', 'preferencesuser', get_string('preferencesuser', 'quiz_overview'));
* @author Martin Dougiamas, Tim Hunt and others.
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package quiz
- *//** */
+ */
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->dirroot.'/mod/quiz/report/overview/overviewsettings_form.php');
function display($quiz, $cm, $course) {
global $CFG, $COURSE, $DB;
- $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+ $this->context = get_context_instance(CONTEXT_MODULE, $cm->id);
// Work out some display options - whether there is feedback, and whether scores should be shown.
$hasfeedback = quiz_has_feedback($quiz->id) && $quiz->grade > 1.e-7 && $quiz->sumgrades > 1.e-7;
$fakeattempt = new stdClass();
$fakeattempt->preview = false;
$fakeattempt->timefinish = $quiz->timeopen;
- $reviewoptions = quiz_get_reviewoptions($quiz, $fakeattempt, $context);
+ $reviewoptions = quiz_get_reviewoptions($quiz, $fakeattempt, $this->context);
$showgrades = $quiz->grade && $quiz->sumgrades && $reviewoptions->scores;
$download = optional_param('download', '', PARAM_ALPHA);
+
+ /// find out current groups mode
+ $currentgroup = groups_get_activity_group($cm, true);
+ if (!$students = get_users_by_capability($this->context, 'mod/quiz:attempt','','','','','','',false)){
+ $students = array();
+ } else {
+ $students = array_keys($students);
+ }
- if($attemptids = optional_param('attemptid', array(), PARAM_INT)) {
- //attempts need to be deleted
- require_capability('mod/quiz:deleteattempts', $context);
- $attemptids = optional_param('attemptid', array(), PARAM_INT);
- foreach($attemptids as $attemptid) {
- add_to_log($course->id, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id,
- $attemptid, $cm->id);
- quiz_delete_attempt($attemptid, $quiz);
+ if (empty($currentgroup)) {
+ // all users who can attempt quizzes
+ $allowed = $students;
+ $groupstudents = array();
+ } else {
+ // all users who can attempt quizzes and who are in the currently selected group
+ if (!$groupstudents = get_users_by_capability($this->context, 'mod/quiz:attempt','','','','',$currentgroup,'',false)){
+ $groupstudents = array();
+ } else {
+ $groupstudents = array_keys($groupstudents);
+ }
+ $allowed = $groupstudents;
+ }
+
+ if (empty($currentgroup)||$groupstudents) {
+ if (optional_param('delete', 0, PARAM_BOOL)){
+ if($attemptids = optional_param('attemptid', array(), PARAM_INT)) {
+ //attempts need to be deleted
+ $this->delete_selected_attempts($quiz, $cm, $attemptids, $groupstudents);
+ //No need for a redirect, any attemptids that do not exist are ignored.
+ //So no problem if the user refreshes and tries to delete the same attempts
+ //twice.
+ }
+ } else if (optional_param('regrade', 0, PARAM_BOOL)){
+ if($attemptids = optional_param('attemptid', array(), PARAM_INT)) {
+ $this->regrade_selected_attempts($quiz, $attemptids, $groupstudents);
+ //No need for a redirect, any attemptids that do not exist are ignored.
+ //So no problem if the user refreshes and tries to delete the same attempts
+ //twice.
+ }
}
- //No need for a redirect, any attemptids that do not exist are ignored.
- //So no problem if the user refreshes and tries to delete the same attempts
- //twice.
}
$qmsubselect = quiz_report_qm_filter_select($quiz);
-
- /// find out current groups mode
- $currentgroup = groups_get_activity_group($cm, true);
-
$mform = new mod_quiz_report_overview_settings($reporturl, array('qmsubselect'=> $qmsubselect, 'quiz'=>$quiz, 'currentgroup'=>$currentgroup));
if ($fromform = $mform->get_data()){
+ $regradeall = false;
+ $regradealldry = false;
+ $regradealldrydo = false;
$attemptsmode = $fromform->attemptsmode;
if ($qmsubselect){
//control is not on the form if
} else {
$qmfilter = 0;
}
+ $regradefilter = $fromform->regradefilter;
set_user_preference('quiz_report_overview_detailedmarks', $fromform->detailedmarks);
set_user_preference('quiz_report_pagesize', $fromform->pagesize);
$detailedmarks = $fromform->detailedmarks;
$pagesize = $fromform->pagesize;
} else {
- $qmfilter = optional_param('qmfilter', 0, PARAM_INT);
+ $regradeall = optional_param('regradeall', 0, PARAM_BOOL);
+ $regradealldry = optional_param('regradealldry', 0, PARAM_BOOL);
+ $regradealldrydo = optional_param('regradealldrydo', 0, PARAM_BOOL);
$attemptsmode = optional_param('attemptsmode', null, PARAM_INT);
+ if ($qmsubselect){
+ $qmfilter = optional_param('qmfilter', 0, PARAM_INT);
+ } else {
+ $qmfilter = 0;
+ }
+ $regradefilter = optional_param('regradefilter', 0, PARAM_INT);
if ($attemptsmode === null){
//default
$attemptsmode = QUIZ_REPORT_ATTEMPTS_ALL;
}
// We only want to show the checkbox to delete attempts
// if the user has permissions and if the report mode is showing attempts.
- $candelete = has_capability('mod/quiz:deleteattempts', $context)
+ $candelete = has_capability('mod/quiz:deleteattempts', $this->context)
&& ($attemptsmode!= QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO);
$displayoptions = array();
$displayoptions['attemptsmode'] = $attemptsmode;
$displayoptions['qmfilter'] = $qmfilter;
+ $displayoptions['regradefilter'] = $regradefilter;
//work out the sql for this table.
- if (!$students = get_users_by_capability($context, 'mod/quiz:attempt','','','','','','',false)){
- $students = array();
- } else {
- $students = array_keys($students);
- }
-
- if (empty($currentgroup)) {
- // all users who can attempt quizzes
- $allowed = $students;
- $groupstudents = array();
- } else {
- // all users who can attempt quizzes and who are in the currently selected group
- if (!$groupstudents = get_users_by_capability($context, 'mod/quiz:attempt','','','','',$currentgroup,'',false)){
- $groupstudents = array();
- } else {
- $groupstudents = array_keys($groupstudents);
- }
- $allowed = $groupstudents;
- }
-
if ($detailedmarks) {
$questions = quiz_report_load_questions($quiz);
} else {
// Only print headers if not asked to download data
$this->print_header_and_tabs($cm, $course, $quiz, "overview");
}
-
+
+ if ($regradeall){
+ $this->regrade_all(false, $quiz, $groupstudents);
+ } else if ($regradealldry){
+ $this->regrade_all(true, $quiz, $groupstudents);
+ } else if ($regradealldrydo){
+ $this->regrade_all_needed($quiz, $groupstudents);
+ }
+ if ($regradeall || $regradealldry || $regradealldrydo){
+ redirect($reporturl->out(false, $displayoptions), '', 5);
+ }
+
if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used
if (!$table->is_downloading()) {
groups_print_activity_menu($cm, $reporturl->out(false, $displayoptions));
}
}
+
+
// Print information on the number of existing attempts
if (!$table->is_downloading()) { //do not print notices when downloading
if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) {
$mform->set_data($displayoptions +compact('detailedmarks', 'pagesize'));
$mform->display();
}
+
+ $countregradeneeded = $this->count_regrade_all_needed($quiz, $groupstudents);
+ //regrade buttons
+ if ($currentgroup){
+ $a= new object();
+ $a->groupname = groups_get_group_name($currentgroup);
+ $a->coursestudents = $COURSE->students;
+ $a->countregradeneeded = $countregradeneeded;
+ $regradealldrydolabel = get_string('regradealldrydogroup', 'quiz_overview', $a);
+ $regradealldrylabel = get_string('regradealldrygroup', 'quiz_overview', $a);
+ $regradealllabel = get_string('regradeallgroup', 'quiz_overview', $a);
+ } else {
+ $regradealldrydolabel = get_string('regradealldrydo', 'quiz_overview', $countregradeneeded);
+ $regradealldrylabel = get_string('regradealldry', 'quiz_overview');
+ $regradealllabel = get_string('regradeall', 'quiz_overview');
+ }
+
+
+ echo '<div class="mdl-align">';
+ echo '<form action="'.$reporturl->out(true).'">';
+ echo '<div>';
+ echo $reporturl->hidden_params_out(array(), 0, $displayoptions);
+ echo '<input type="submit" name="regradeall" value="'.$regradealllabel.'"/>';
+ echo '<input type="submit" name="regradealldry" value="'.$regradealldrylabel.'"/>';
+ if ($countregradeneeded){
+ echo '<input type="submit" name="regradealldrydo" value="'.$regradealldrydolabel.'"/>';
+ }
+ echo '</div>';
+ echo '</form>';
+ echo '</div>';
if (!$nostudents || ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL)){
// Print information on the grading method and whether we are displaying
($qmsubselect?"($qmsubselect) AS gradedattempt, ":'').
'qa.uniqueid AS attemptuniqueid, qa.id AS attempt, u.id AS userid, u.idnumber, u.firstname, u.lastname, u.picture, '.
'qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration ';
-
+
// This part is the same for all cases - join users and quiz_attempts tables
$from = '{user} u ';
$from .= 'LEFT JOIN {quiz_attempts} qa ON qa.userid = u.id AND qa.quiz = :quizid';
if ($qmsubselect && $qmfilter){
$from .= ' AND '.$qmsubselect;
}
- switch ($attemptsmode){
+ switch ($attemptsmode){
case QUIZ_REPORT_ATTEMPTS_ALL:
// Show all attempts, including students who are no longer in the course
$where = 'qa.id IS NOT NULL AND qa.preview = 0';
}
$table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);
-
-
-
+
+ $sqlobject = new object;
+ $sqlobject->from = $from;
+ $sqlobject->where = $where;
+ $sqlobject->params = $params;
+ //test to see if there are any regraded attempts to be listed.
+ if (quiz_get_regraded_qs($sqlobject, 0, 1)){
+ $regradedattempts = true;
+ } else {
+ $regradedattempts = false;
+ }
+ $fields .= ', COALESCE((SELECT MAX(qqr.regraded) FROM {quiz_question_regrade} qqr WHERE qqr.attemptid = qa.uniqueid),-1) AS regraded';
+ if ($regradefilter){
+ $where .= ' AND COALESCE((SELECT MAX(qqr.regraded) FROM {quiz_question_regrade} qqr WHERE qqr.attemptid = qa.uniqueid),-1) !=\'-1\'';
+ }
$table->set_sql($fields, $from, $where, $params);
// Define table columns
foreach ($questions as $id => $question) {
// Ignore questions of zero length
$columns[] = 'qsgrade'.$id;
- $headers[] = '#'.$question->number;
+ $header = '#'.$question->number;
+ if (!$table->is_downloading()) {
+ $header .='<br />';
+ } else {
+ $header .=' ';
+ }
+ $header .='--/'.quiz_rescale_grade($question->grade, $quiz);
+ $headers[] = $header;
}
}
-
+ if ($regradedattempts){
+ $columns[] = 'regraded';
+ $headers[] = get_string('regrade', 'quiz_overview');
+ }
if ($showgrades) {
$columns[] = 'sumgrades';
$headers[] = get_string('grade', 'quiz').'/'.$quiz->grade;
// Set up the table
$table->define_baseurl($reporturl->out(false, $displayoptions));
- $table->collapsible(true);
+ $table->collapsible(false);
$table->column_suppress('picture');
$table->column_suppress('fullname');
}
return true;
}
+ /**
+ * @param bool changedb whether to change contents of state and grades
+ * tables.
+ */
+ function regrade_all($dry, $quiz, $groupstudents){
+ global $DB;
+ if (!has_capability('mod/quiz:grade', $this->context)) {
+ notify(get_string('regradenotallowed', 'quiz'));
+ return true;
+ }
+ // Fetch all attempts
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $select = "userid $usql AND ";
+ } else {
+ $select = '';
+ $params = array();
+ }
+ $select .= "quiz = ? AND preview = 0";
+ $params[] = $quiz->id;
+ if (!$attempts = $DB->get_records_select('quiz_attempts', $select, $params)) {
+ print_heading(get_string('noattempts', 'quiz'));
+ return true;
+ }
+
+ $this->clear_regrade_table($quiz, $groupstudents);
+
+ // Fetch all questions
+ $questions = question_load_questions(quiz_questions_in_quiz($quiz->questions), 'qqi.grade AS maxgrade, qqi.id AS instance',
+ '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');
+
+ // Print heading
+ print_heading(get_string('regradingquiz', 'quiz', format_string($quiz->name)));
+ $qstodo = count($questions);
+ $qsdone = 0;
+ if ($qstodo > 1){
+ $qpb = new progress_bar('qregradingbar', 500, true);
+ $qpb->update($qsdone, $qstodo, "Question $qsdone of $qstodo");
+ }
+ $apb = new progress_bar('aregradingbar', 500, true);
+
+ // Loop through all questions and all attempts and regrade while printing progress info
+ $attemptstodo = count($attempts);
+ foreach ($questions as $question) {
+ $attemptsdone = 0;
+ $apb->restart();
+ echo '<p class="mdl-align"><strong>'.get_string('regradingquestion', 'quiz', $question->name).'</strong></p>';
+ @flush();@ob_flush();
+ foreach ($attempts as $attempt) {
+ set_time_limit(30);
+ $changed = regrade_question_in_attempt($question, $attempt, $quiz, true, $dry);
+
+ $attemptsdone++;
+ $a = new object();
+ $a->done = $attemptsdone;
+ $a->todo = $attemptstodo;
+ $apb->update($attemptsdone, $attemptstodo, get_string('attemptprogress', 'quiz_overview', $a));
+ }
+ $qsdone++;
+ if (isset($qpb)){
+ $a = new object();
+ $a->done = $qsdone;
+ $a->todo = $qstodo;
+ $qpb->update($qsdone, $qstodo, get_string('qprogress', 'quiz_overview', $a));
+ }
+ // the following makes sure that the output is sent immediately.
+ @flush();@ob_flush();
+ }
+
+ if (!$dry){
+ $this->check_overall_grades($quiz, $groupstudents);
+ }
+ }
+ function count_regrade_all_needed($quiz, $groupstudents){
+ global $DB;
+ // Fetch all attempts that need regrading
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $where = "qa.userid $usql AND ";
+ } else {
+ $where = '';
+ $params = array();
+ }
+ $where .= "qa.quiz = ? AND qa.preview = 0 AND qa.uniqueid = qqr.attemptid AND qqr.regraded = 0";
+ $params[] = $quiz->id;
+ return $DB->get_field_sql('SELECT COUNT(1) FROM {quiz_attempts} qa, {quiz_question_regrade} qqr WHERE '. $where, $params);
+ }
+ function regrade_all_needed($quiz, $groupstudents){
+ global $DB;
+ if (!has_capability('mod/quiz:grade', $this->context)) {
+ notify(get_string('regradenotallowed', 'quiz'));
+ return;
+ }
+ // Fetch all attempts that need regrading
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $where = "qa.userid $usql AND ";
+ } else {
+ $where = '';
+ $params = array();
+ }
+ $where .= "qa.quiz = ? AND qa.preview = 0 AND qa.uniqueid = qqr.attemptid AND qqr.regraded = 0";
+ $params[] = $quiz->id;
+ if (!$attempts = $DB->get_records_sql('SELECT qa.*, qqr.questionid FROM {quiz_attempts} qa, {quiz_question_regrade} qqr WHERE '. $where, $params)) {
+ print_heading(get_string('noattemptstoregrade', 'quiz_overview'));
+ return true;
+ }
+ $this->clear_regrade_table($quiz, $groupstudents);
+ // Fetch all questions
+ $questions = question_load_questions(quiz_questions_in_quiz($quiz->questions), 'qqi.grade AS maxgrade, qqi.id AS instance',
+ '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');
+
+ // Print heading
+ print_heading(get_string('regradingquiz', 'quiz', format_string($quiz->name)));
+
+ $apb = new progress_bar('aregradingbar', 500, true);
+
+ // Loop through all questions and all attempts and regrade while printing progress info
+ $attemptstodo = count($attempts);
+ $attemptsdone = 0;
+ @flush();@ob_flush();
+ $attemptschanged = array();
+ foreach ($attempts as $attempt) {
+ $question = $questions[$attempt->questionid];
+ $changed = regrade_question_in_attempt($question, $attempt, $quiz, true);
+ if ($changed){
+ $attemptschanged[] = $attempt->uniqueid;
+ $usersschanged[] = $attempt->userid;
+ }
+ if (!empty($apb)){
+ $attemptsdone++;
+ $a = new object();
+ $a->done = $attemptsdone;
+ $a->todo = $attemptstodo;
+ $apb->update($attemptsdone, $attemptstodo, get_string('attemptprogress', 'quiz_overview', $a));
+ }
+ }
+ $this->check_overall_grades($quiz, array(), $attemptschanged);
+ }
+
+ function clear_regrade_table($quiz, $groupstudents){
+ global $DB;
+ // Fetch all attempts that need regrading
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $where = "qa.userid $usql AND ";
+ } else {
+ $usql = '';
+ $where = '';
+ $params = array();
+ }
+ $params[] = $quiz->id;
+ $delsql = 'DELETE FROM qqr USING {quiz_question_regrade} qqr, {quiz_attempts} qa WHERE qqr.attemptid = qa.uniqueid AND ';
+ if ($usql){
+ $delsql .= "qa.userid $usql AND ";
+ }
+ $delsql .='qa.quiz=?';
+ if (!$DB->execute($delsql, $params)){
+ print_error('err_failedtodeleteregrades', 'quiz_overview');
+ }
+ }
+
+ 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} qa SET qa.sumgrades= " .
+ "(SELECT SUM(qs.grade) FROM {question_sessions} qns, {question_states} qs " .
+ "WHERE qns.newgraded = qs.id AND qns.attemptid = qa.uniqueid ) WHERE ";
+ $attemptsql='';
+ if (!$attemptids){
+ if ($userids){
+ list($usql, $params) = $DB->get_in_or_equal($userids);
+ $attemptsql .= "qa.userid $usql AND ";
+ } else {
+ $params = array();
+ }
+ $attemptsql .= "qa.quiz =? AND preview = 0";
+ $params[] = $quiz->id;
+ } else {
+ list($asql, $params) = $DB->get_in_or_equal($attemptids);
+ $attemptsql .= "qa.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 qa.userid FROM {quiz_attempts} qa WHERE $attemptsql)";
+ } else {
+ $sql = "SELECT qa.* FROM {quiz_attempts} qa WHERE $attemptsql";
+ }
+ 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);
+ }
+ }
+ }
+ function delete_selected_attempts($quiz, $cm, $attemptids, $groupstudents){
+ global $DB, $COURSE;
+ require_capability('mod/quiz:deleteattempts', $this->context);
+ $attemptids = optional_param('attemptid', array(), PARAM_INT);
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $where = "qa.userid $usql AND ";
+ }
+ foreach($attemptids as $attemptid) {
+ add_to_log($COURSE->id, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id,
+ $attemptid, $cm->id);
+ quiz_delete_attempt($attemptid, $quiz);
+ }
+ }
+ function regrade_selected_attempts($quiz, $attemptids, $groupstudents){
+ global $DB;
+ require_capability('mod/quiz:grade', $this->context);
+ if ($groupstudents){
+ list($usql, $params) = $DB->get_in_or_equal($groupstudents);
+ $where = "qa.userid $usql AND ";
+ } else {
+ $params = array();
+ $where = '';
+ }
+ list($asql, $aparams) = $DB->get_in_or_equal($attemptids);
+ $where = "qa.id $asql AND ";
+ $params = array_merge($params, $aparams);
+
+ $where .= "qa.quiz = ? AND qa.preview = 0";
+ $params[] = $quiz->id;
+ if (!$attempts = $DB->get_records_sql('SELECT qa.* FROM {quiz_attempts} qa WHERE '. $where, $params)) {
+ print_error('noattemptstoregrade', 'quiz_overview');
+ }
+
+ // Fetch all questions
+ $questions = question_load_questions(quiz_questions_in_quiz($quiz->questions), 'qqi.grade AS maxgrade, qqi.id AS instance',
+ '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');
+ $updateoverallgrades = array();
+ foreach($attempts as $attempt) {
+ foreach ($questions as $question){
+ $changed = regrade_question_in_attempt($question, $attempt, $quiz, true);
+ }
+ $updateoverallgrades[] = $attempt->uniqueid;
+ }
+ $this->check_overall_grades($quiz, array(), $updateoverallgrades);
+ }
}
--- /dev/null
+<?php // $Id$
+
+////////////////////////////////////////////////////////////////////////////////
+// Code fragment to define the version of quiz overview report
+// This fragment is called by moodle_needs_upgrading() and /admin/index.php
+////////////////////////////////////////////////////////////////////////////////
+
+$plugin->version = 2008062700; // The (date) version of this module
+
+?>
return array();
}
if ($idxattemptq){
- $gradedstatesbyattempt = array();
- foreach ($gradedstates as $gradedstate){
- if (!isset($gradedstatesbyattempt[$gradedstate->attempt])){
- $gradedstatesbyattempt[$gradedstate->attempt] = array();
- }
- $gradedstatesbyattempt[$gradedstate->attempt][$gradedstate->question] = $gradedstate;
- }
- return $gradedstatesbyattempt;
+ return quiz_report_index_by_keys($gradedstates, array('attempt', 'question'));
} else {
return $gradedstates;
}
}
+function quiz_report_index_by_keys($datum, $keys){
+ if (!$datum){
+ return $datum;
+ }
+ $key = array_shift($keys);
+ $datumkeyed = array();
+ foreach ($datum as $data){
+ if ($keys){
+ $datumkeyed[$data->{$key}][]= $data;
+ } else {
+ $datumkeyed[$data->{$key}]= $data;
+ }
+ }
+ if ($keys){
+ foreach ($datumkeyed as $datakey => $datakeyed){
+ $datumkeyed[$datakey] = quiz_report_index_by_keys($datakeyed, $keys);
+ }
+ }
+ return $datumkeyed;
+}
+function quiz_get_regraded_qs($attemptidssql, $limitfrom=0, $limitnum=0){
+ global $CFG, $DB;
+ if ($attemptidssql && is_array($attemptidssql)){
+ list($asql, $params) = $DB->get_in_or_equal($attemptidssql);
+ $regradedqsql = "SELECT qqr.* FROM " .
+ "{quiz_question_regrade} qqr " .
+ "WHERE qqr.attemptid $asql";
+ $regradedqs = $DB->get_records_sql($regradedqsql, $params, $limitfrom, $limitnum);
+ } else if ($attemptidssql && is_object($attemptidssql)){
+ $regradedqsql = "SELECT qqr.* FROM " .
+ $attemptidssql->from.", ".
+ "{quiz_question_regrade} qqr " .
+ "WHERE qqr.attemptid = qa.uniqueid AND " .
+ $attemptidssql->where;
+ $regradedqs = $DB->get_records_sql($regradedqsql, $attemptidssql->params, $limitfrom, $limitnum);
+ } else {
+ return array();
+ }
+ return quiz_report_index_by_keys($regradedqs, array('attemptid', 'questionid'));
+}
function quiz_get_average_grade_for_questions($quiz, $userids){
global $CFG, $DB;
$qmfilter = quiz_report_qm_filter_select($quiz);
}
#mod-quiz-report table#attempts,
-#mod-quiz-report table#commands,
+#mod-quiz-report div#commands,
#mod-quiz-report table#itemanalysis
{
width: 80%;