]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-14216 "Improvements to regrade report - improved reporting of actions and dry...
authorjamiesensei <jamiesensei>
Fri, 11 Jul 2008 07:27:14 +0000 (07:27 +0000)
committerjamiesensei <jamiesensei>
Fri, 11 Jul 2008 07:27:14 +0000 (07:27 +0000)
12 files changed:
lang/en_utf8/quiz_overview.php
lib/questionlib.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/report/overview/db/install.xml [new file with mode: 0644]
mod/quiz/report/overview/db/upgrade.php [new file with mode: 0644]
mod/quiz/report/overview/overview_table.php
mod/quiz/report/overview/overviewsettings_form.php
mod/quiz/report/overview/report.php
mod/quiz/report/overview/version.php [new file with mode: 0644]
mod/quiz/report/reportlib.php
theme/standard/styles_layout.css

index dddc39ab41e115b52cae0ce6bf3b88c5033f8f3c..d7c1bd1e04d2e7a51a84d795f63ec0709907746c 100644 (file)
@@ -7,15 +7,23 @@ $string['allattemptscontributetograde'] = 'All attempts contribute to final grad
 $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';
@@ -24,7 +32,18 @@ $string['pagesize'] = 'Page size';
 $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';
index ce91e500f3013943c7856e38668fc5a464a326e0..9e7f56ad07109cd47879ab99a2537199151c8b9d 100644 (file)
@@ -1233,8 +1233,10 @@ function question_get_feedback_class($fraction) {
 * @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
@@ -1280,43 +1282,67 @@ function regrade_question_in_attempt($question, $attempt, $cmoptions, $verbose=f
                 // 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;
 }
index a8d16f57c00db6624195c2e96fdd075ddc9048bd..530f48b87504e0767dd1a4fdfc388e9214789feb 100644 (file)
@@ -242,7 +242,7 @@ function quiz_cron () {
  * @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' => '',
@@ -253,8 +253,15 @@ function quiz_get_user_attempts($quizid, $userid, $status = 'finished', $include
     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 {
index 7215e741c3e6372d34b7e2e4c8d45f6638201a85..be9209536cdb4d5c341227913a00c9e83b4588d3 100644 (file)
@@ -516,9 +516,12 @@ function quiz_set_grade($newgrade, &$quiz) {
  *
  * @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;
 
@@ -526,10 +529,12 @@ function quiz_save_best_grade($quiz, $userid = null) {
         $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
diff --git a/mod/quiz/report/overview/db/install.xml b/mod/quiz/report/overview/db/install.xml
new file mode 100644 (file)
index 0000000..ab6834a
--- /dev/null
@@ -0,0 +1,21 @@
+<?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
diff --git a/mod/quiz/report/overview/db/upgrade.php b/mod/quiz/report/overview/db/upgrade.php
new file mode 100644 (file)
index 0000000..bee8b66
--- /dev/null
@@ -0,0 +1,41 @@
+<?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;
+}
+
+?>
index 94a264a3c9e39589c0679819e60f3a460e411773..68d17253adbe0a482b484a0bc534b434694ca3e4 100644 (file)
@@ -72,10 +72,8 @@ class quiz_report_overview_table extends table_sql {
         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>';
@@ -87,15 +85,16 @@ class quiz_report_overview_table extends table_sql {
         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 '&nbsp;&nbsp;';
-                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>';
@@ -160,6 +159,22 @@ class quiz_report_overview_table extends table_sql {
         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.'&amp;attempt='.$attempt->attempt.'">'.$grade.'</a>';
                 if ($this->qmsubselect && $attempt->gradedattempt){
                     $gradehtml = '<div class="highlight">'.$gradehtml.'</div>';
@@ -173,39 +188,35 @@ class quiz_report_overview_table extends table_sql {
         }
     }
     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.'&amp;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.'&amp;number='.$question->number,
+                            'reviewquestion', $grade, 450, 650, get_string('reviewresponse', 'quiz'),
+                            'none', true);
+                } else {
+                    return $grade;
+                }
             } else {
-                return $grade;
+                return '--';
             }
         } else {
             return NULL;
@@ -224,7 +235,15 @@ class quiz_report_overview_table extends table_sql {
         }
 
     }
-    
+    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
@@ -250,6 +269,23 @@ class quiz_report_overview_table extends table_sql {
             }
         }
         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);
+            }
+        }
     }
 }
 ?>
index e250e0aa67593ece0e2f6080deaea13d5600f97a..55e97198f5f3d30da06a3a702bb478a2d2947b24 100644 (file)
@@ -27,10 +27,15 @@ class mod_quiz_report_overview_settings extends moodleform {
             $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'));
 
index fad12b6e3d27ce425cf5c76730b0185eec932d15..972a82f122024ccffd68a418aaa079278ed5e398 100644 (file)
@@ -6,7 +6,7 @@
  * @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');
@@ -20,30 +20,57 @@ class quiz_overview_report extends quiz_default_report {
     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.
         }
 
 
@@ -56,12 +83,11 @@ class quiz_overview_report extends quiz_default_report {
         $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
@@ -71,13 +97,22 @@ class quiz_overview_report extends quiz_default_report {
             } 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;
@@ -101,35 +136,16 @@ class quiz_overview_report extends quiz_default_report {
         }
         // 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 {
@@ -143,12 +159,25 @@ class quiz_overview_report extends quiz_default_report {
             // 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)) {
@@ -168,6 +197,36 @@ class quiz_overview_report extends quiz_default_report {
             $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
@@ -188,7 +247,7 @@ class quiz_overview_report extends quiz_default_report {
                 ($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';
@@ -197,7 +256,7 @@ class quiz_overview_report extends quiz_default_report {
             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';
@@ -223,9 +282,21 @@ class quiz_overview_report extends quiz_default_report {
              }
     
             $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
@@ -270,10 +341,20 @@ class quiz_overview_report extends quiz_default_report {
                 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;
@@ -291,7 +372,7 @@ class quiz_overview_report extends quiz_default_report {
             // 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');
@@ -319,6 +400,255 @@ class quiz_overview_report extends quiz_default_report {
         }
         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);
+    }
 
 }
 
diff --git a/mod/quiz/report/overview/version.php b/mod/quiz/report/overview/version.php
new file mode 100644 (file)
index 0000000..a69690d
--- /dev/null
@@ -0,0 +1,10 @@
+<?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
+
+?>
index 4a61787931c0a20431695d086174065f8d7e2321..a65353ea9cdb74c4a0785350aacfdff35e2b2062 100644 (file)
@@ -39,19 +39,52 @@ function quiz_get_newgraded_states($attemptidssql, $idxattemptq = true, $fields=
         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);
index b2d1e898c71e2f458903e7eb49fcce6895aef8f3..6741f441983ff22f71055c5e2c0684dd0df0bed8 100644 (file)
@@ -3918,7 +3918,7 @@ table.quizreviewsummary td.cell {
 }
 
 #mod-quiz-report table#attempts,
-#mod-quiz-report table#commands,
+#mod-quiz-report div#commands,
 #mod-quiz-report table#itemanalysis
 {
   width: 80%;