]> git.mjollnir.org Git - moodle.git/commitdiff
MDL-9137 new grade_grades field overridden; item edit form fixes; other minor fixes...
authorskodak <skodak>
Tue, 10 Jul 2007 18:08:24 +0000 (18:08 +0000)
committerskodak <skodak>
Tue, 10 Jul 2007 18:08:24 +0000 (18:08 +0000)
grade/export/lib.php
grade/report/grader/edit_feedback_form.php
grade/report/grader/edit_item.php
grade/report/grader/edit_item_form.php
grade/report/grader/index.php
lib/grade/grade_category.php
lib/grade/grade_grades.php
lib/grade/grade_item.php
lib/gradelib.php
lib/simpletest/grade/simpletest/testgradeitem.php
theme/standard/styles_color.css

index 46caa062a09de41466ac1c36a1d2642dc3cc3faf..d241a79f133bb0f87022d0e08ef2e9c4224b27f2 100755 (executable)
@@ -84,7 +84,7 @@ class grade_export {
 
         // first make sure we have all final grades
         // TODO: check that no grade_item has needsupdate set
-        grade_update_final_grades($id);
+        grade_regrade_final_grades($id);
 
         /// Check to see if groups are being used in this course
         if ($groupmode = groupmode($course)) {   // Groups are being used
index 104f556534c670dd41700dec5708dd817e5a96f4..dbd54c565bc7e978f7fbba0391ac440a87985e17 100644 (file)
@@ -8,7 +8,7 @@ class edit_feedback_form extends moodleform {
         $mform =& $this->_form;
 
         $feedbackformat = get_user_preferences('grade_report_feedbackformat', $CFG->grade_report_feedbackformat);
-        
+
         // visible elements
         // User preference determines the format
         if ($CFG->htmleditor && $USER->htmleditor && $feedbackformat == GRADER_REPORT_FEEDBACK_FORMAT_HTML) {
index 9448e019e3af27518e3a15133932a6d5152b787e..d42429eea38178009a94f38b03c071616fae17af 100644 (file)
@@ -31,10 +31,6 @@ if ($mform->is_cancelled()) {
     redirect($returnurl);
 
 } else if ($data = $mform->get_data()) {
-    if (empty($data->checkbox)) {
-        $data->checkbox = 0; // work around the missing value if checkbox not selected
-    }
-
     if (array_key_exists('calculation', $data)) {
         $data->calculation = grade_item::normalize_formula($data->calculation, $course->id);
     }
index 518b426cda5a6332f663b8da7b18483b4c41d7e8..9670c61d659d2170c35a5b1a33a302b85ef8d463 100644 (file)
@@ -42,14 +42,20 @@ class edit_item_form extends moodleform {
             }
         }
         $mform->addElement('select', 'scaleid', get_string('scale'), $options);
-        $mform->disabledIf('scaleid', 'gradetype', 'noteq', GRADE_TYPE_SCALE);
+        $mform->disabledIf('scaleid', 'gradetype', 'eq', GRADE_TYPE_TEXT);
+        $mform->disabledIf('scaleid', 'gradetype', 'eq', GRADE_TYPE_NONE);
+        $mform->disabledIf('scaleid', 'gradetype', 'eq', GRADE_TYPE_VALUE);
 
         $mform->addElement('text', 'grademax', get_string('grademax', 'grades'));
-        $mform->disabledIf('grademax', 'gradetype', 'noteq', GRADE_TYPE_VALUE);
-        $mform->setDefault('grademin', 100);
+        $mform->disabledIf('grademax', 'gradetype', 'eq', GRADE_TYPE_TEXT);
+        $mform->disabledIf('grademax', 'gradetype', 'eq', GRADE_TYPE_NONE);
+        $mform->disabledIf('grademax', 'gradetype', 'eq', GRADE_TYPE_SCALE);
+        $mform->setDefault('grademax', 100);
 
         $mform->addElement('text', 'grademin', get_string('grademin', 'grades'));
-        $mform->disabledIf('grademin', 'gradetype', 'noteq', GRADE_TYPE_VALUE);
+        $mform->disabledIf('grademin', 'gradetype', 'eq', GRADE_TYPE_TEXT);
+        $mform->disabledIf('grademin', 'gradetype', 'eq', GRADE_TYPE_NONE);
+        $mform->disabledIf('grademin', 'gradetype', 'eq', GRADE_TYPE_SCALE);
         $mform->setDefault('grademin', 0);
 
         $mform->addElement('text', 'gradepass', get_string('gradepass', 'grades'));
@@ -64,7 +70,7 @@ class edit_item_form extends moodleform {
         $mform->disabledIf('plusfactor', 'gradetype', 'eq', GRADE_TYPE_NONE);
         $mform->setDefault('plusfactor', 0);
 
-        $mform->addElement('checkbox', 'locked', get_string('locked', 'grades'));
+        $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades'));
 
         $mform->addElement('date_time_selector', 'locktime', get_string('locktime', 'grades'), array('optional'=>true));
         $mform->disabledIf('locktime', 'gradetype', 'eq', GRADE_TYPE_NONE);
@@ -93,10 +99,14 @@ class edit_item_form extends moodleform {
 
         if ($id = $mform->getElementValue('id')) {
             $grade_item = grade_item::fetch(array('id'=>$id));
-            if (!in_array($grade_item->itemtype, array('manual', 'course', 'category'))) {
+            if ($grade_item->is_normal_item()) {
                 // following items are set up from modules and should not be overrided by user
                 $mform->hardFreeze('itemname,idnumber,calculation,gradetype,grademax,grademin,scaleid');
             }
+            if ($grade_item->is_manual_item()) {
+                // manual grade item does not use these - uses only final grades
+                $mform->hardFreeze('plusfactor,multfactor');
+            }
         }
     }
 
@@ -112,6 +122,13 @@ class edit_item_form extends moodleform {
             }
         }
 
+        if (array_key_exists('grademin', $data) and array_key_exists('grademax', $data)) {
+            if ($data['grademax'] == $data['grademin'] or $data['grademax'] < $data['grademin']) {
+                $errors['grademin'] = get_String('incorrectminmax', 'grades');
+                $errors['grademax'] = get_String('incorrectminmax', 'grades');
+            }
+        }
+
         if (0 == count($errors)){
             return true;
         } else {
index a4dbfed383203bc31c8bbda9dc54ca6ac0f0ec51..d976302aa5765f186e6978ab8b3884260bf25e50 100644 (file)
@@ -3,17 +3,29 @@
 /// This creates and handles the whole grader report interface, sans header and footer
 
 require_once($CFG->libdir.'/tablelib.php');
-include_once($CFG->libdir.'/gradelib.php');
+require_once($CFG->libdir.'/gradelib.php');
 
+$gradeserror = array();
 
 /**
- * format number using lang specific decimal point and thousand separator
+ * format grade using lang specific decimal point and thousand separator
+ * the result is suitable for printing on html page
  * @param float $gradeval raw grade value pulled from db
  * @return string $gradeval formatted grade value
  */
 function get_grade_clean($gradeval) {
     global $CFG;
-    
+
+    if (is_null($gradeval)) {
+        $gradeval = '';
+       } else {
+        // decimal points as specified by user
+        $decimals = get_user_preferences('grade_report_decimalpoints', $CFG->grade_report_decimalpoints);
+        $gradeval = number_format($gradeval, $decimals, get_string('decpoint', 'langconfig'), get_string('thousandsep', 'langconfig'));
+    }
+
+    return $gradeval;
+
     /*
     // commenting this out, if this is added, we also need to find the number of decimal place preserved
     // so it can go into number_format
@@ -23,11 +35,7 @@ function get_grade_clean($gradeval) {
         $gradeval = 0;
     }
     */
-    // decimal points as specified by user
-    $decimals = get_user_preferences('grade_report_decimalpoints', $CFG->grade_report_decimalpoints);
-    $gradeval = number_format($gradeval, $decimals, get_string('decpoint', 'langconfig'), get_string('thousandsep', 'langconfig'));
 
-    return $gradeval;
 }
 
 /**
@@ -97,15 +105,18 @@ function grader_report_print_toggle($type, $baseurl, $return=false) {
     }
 }
 
+
 /// processing posted grades here
 
-if ($data = data_submitted()) {
-    foreach ($data as $varname => $postedgrade) {
+if ($data = data_submitted() and confirm_sesskey()) {
 
-        // clean posted values
-        $postedgrade = clean_param($postedgrade, PARAM_RAW); 
-        // can not use param number here, because we can have "," in grade
-        $varname = clean_param($varname, PARAM_RAW);
+    // always initialize all arrays
+    $queue = array();
+
+    foreach ($data as $varname => $postedgrade) {
+        // this is a bit tricky - we have to first load all grades into memory,
+        // check if changed and only then start updating the final grades because
+        // columns might depend one on another - the result would be overriden calculated and category grades
 
         // skip, not a grade
         if (!strstr($varname, 'grade')) {
@@ -114,40 +125,54 @@ if ($data = data_submitted()) {
 
         $gradeinfo = explode("_", $varname);
 
-        $grade = new object();
-        $grade->userid = clean_param($gradeinfo[1], PARAM_INT);
-        $gradeitemid = clean_param($gradeinfo[2], PARAM_INT);
-        // grade needs to formatted to proper format for storage
-        $grade->rawgrade = format_grade($postedgrade);
+        $userid = clean_param($gradeinfo[1], PARAM_INT);
+        $itemid = clean_param($gradeinfo[2], PARAM_INT);
 
-        // put into grades array
-        $grades[$gradeitemid][] = $grade;
-    }
-}
-
-// array to hold all error found during grade processing, e.g. outofrange
-$gradeserror = array();
+        if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$course->id))) { // we must verify course id here!
+            error('Incorrect grade item id');
+        }
 
-// now we update the raw grade for each posted grades
-if (!empty($grades)) {
-    foreach ($grades as $gradeitemid => $itemgrades) {
-        foreach ($itemgrades as $gradedata) {
-            $gradeitem = new grade_item(array('id'=>$gradeitemid), true);
-            
-            // cbeck if grade is in range, if not, add to error array
-            // MDL-10369
-            
-            // -1 is accepted for scale grades (no grade)            
-            if ($gradedata->rawgrade == -1 && $gradeitem->gradetype == 2) {
-                $gradeitem->update_raw_grade($gradedata->userid, $gradedata->rawgrade); 
+        if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
+            if ($postedgrade == -1) { // -1 means no grade
+                $finalgrade = null;
             } else {
-                if ($gradeitem->grademax < $gradedata->rawgrade || $gradeitem->grademin > $gradedata->rawgrade) {
-                    $gradeserror[$gradeitem->id][$gradedata->userid] = 'outofrange';
-                } else {
-                    $gradeitem->update_raw_grade($gradedata->userid, $gradedata->rawgrade);
-                }
+                $finalgrade = (float)$postedgrade;
+            }
+        } else {
+            if ($postedgrade == '') { // empty string means no grade
+                $finalgrade = null;
+            } else {
+                $finalgrade = format_grade($postedgrade);
+            }
+        }
+
+        if (!is_null($finalgrade) and ($finalgrade < $grade_item->grademin or $finalgrade > $grade_item->grademax)) {
+            $gradeserror[$grade_item->id][$userid] = 'outofrange'; //TODO: localize
+            // another possiblity is to use bounded number instead
+            continue;
+        }
+
+        if ($grade = grade_grades::fetch(array('userid'=>$userid, 'itemid'=>$grade_item->id))) {
+            if (!is_null($grade->finalgrade)) {
+                $grade->finalgrade = (float)$grade->finalgrade;
+            }
+            if ($grade->finalgrade === $finalgrade) {
+                // we must not update all grades, only changed ones - we do not want to mark everything as overriden
+                continue;
             }
         }
+
+        $gradedata = new object();
+        $gradedata->grade_item = $grade_item;
+        $gradedata->finalgrade = $finalgrade;
+        $gradedata->userid     = $userid;
+
+        $queue[] = $gradedata;
+    }
+
+    // now we update the new final grade for each changed grade
+    foreach ($queue as $gradedata) {
+        $gradedata->grade_item->update_final_grade($gradedata->userid, $gradedata->finalgrade, 'gradebook');
     }
 }
 
@@ -315,7 +340,7 @@ if (!empty($target) && !empty($action) && confirm_sesskey()) {
 
 // first make sure we have all final grades
 // TODO: check that no grade_item has needsupdate set
-grade_update_final_grades($courseid);
+grade_regrade_final_grades($courseid);
 
 // roles to be displaye in the gradebook
 $gradebookroles = $CFG->gradebookroles;
@@ -370,7 +395,7 @@ if (empty($users)) {
 
 // phase 2 sql, we supply the userids in this query, and get all the grades
 // pulls out all the grades, this does not need to worry about paging
-$sql = "SELECT g.id, g.itemid, g.userid, g.finalgrade, g.hidden, g.locked, g.locktime, gt.feedback
+$sql = "SELECT g.id, g.itemid, g.userid, g.finalgrade, g.hidden, g.locked, g.locktime, g.overridden, gt.feedback
         FROM  {$CFG->prefix}grade_items gi,
               {$CFG->prefix}grade_grades g
         LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid
@@ -462,7 +487,7 @@ foreach ($gtree->levels as $key=>$row) {
     $headerhtml .= '<tr class="heading">';
 
     if ($key == $numrows - 1) {
-        $headerhtml .= '<th class="user"><a href="'.$baseurl.'&amp;sortitemid=firstname">Firstname</a> '
+        $headerhtml .= '<th class="user"><a href="'.$baseurl.'&amp;sortitemid=firstname">Firstname</a> ' //TODO: localize
                     . $firstarrow. '/ <a href="'.$baseurl.'&amp;sortitemid=lastname">Lastname </a>'. $lastarrow .'</th>';
     } else {
         $headerhtml .= '<td class="topleft">&nbsp;</td>';
@@ -519,7 +544,7 @@ foreach ($gtree->levels as $key=>$row) {
             } else if ($object->itemtype == 'manual') {
                 //TODO: add manual grading icon
                 $icon = '<img src="'.$CFG->pixpath.'/t/edit.gif" class="icon" alt="'.get_string('manualgrade', 'grades')
-                      .'"/>'; // TODO: localize
+                      .'"/>';
             }
 
 
@@ -546,29 +571,29 @@ foreach ($users as $userid => $user) {
                   . $user->id . '">' . fullname($user) . '</a></th>';
     foreach ($items as $item) {
 
-        $studentshtml .= '<td>';
-
         if (isset($finalgrades[$userid][$item->id])) {
-
-            $gradeval = get_grade_clean($finalgrades[$userid][$item->id]->finalgrade);
-
+            $gradeval = $finalgrades[$userid][$item->id]->finalgrade;
             $grade = new grade_grades($finalgrades[$userid][$item->id], false);
             $grade->feedback = $finalgrades[$userid][$item->id]->feedback;
+
         } else {
-            // if itemtype is course or category, the grades in this item is not directly editable
-            if ($USER->gradeediting && $item->itemtype != 'course' && $item->itemtype != 'category') {
-                $gradeval ='';
-            } else {
-                $gradeval = '-';
-            }
+            $gradeval = null;
             $grade = new grade_grades(array('userid' => $userid, 'itemid' => $item->id), false);
+            $grade->feedback = '';
+        }
+
+        if ($grade->is_overridden()) {
+            $studentshtml .= '<td class="overridden">';
+        } else {
+            $studentshtml .= '<td>';
         }
 
+
         // if in editting mode, we need to print either a text box
         // or a drop down (for scales)
 
         // grades in item of type grade category or course are not directly editable
-        if ($USER->gradeediting && $item->itemtype != 'course' && $item->itemtype != 'category') {
+        if ($USER->gradeediting) {
             // We need to retrieve each grade_grade object from DB in order to
             // know if they are hidden/locked
 
@@ -600,9 +625,9 @@ foreach ($users as $userid => $user) {
                 }
             } else {
                 if ($quickgrading) {
-                    $studentshtml .= '<input size="6" type="text" name="grade_'.$userid.'_'.$item->id.'" value="'.$gradeval.'"/>';
+                    $studentshtml .= '<input size="6" type="text" name="grade_'.$userid.'_'.$item->id.'" value="'.get_grade_clean($gradeval).'"/>';
                 } else {
-                    $studentshtml .= $gradeval;
+                    $studentshtml .= get_grade_clean($gradeval);
                 }
             }
 
@@ -614,8 +639,9 @@ foreach ($users as $userid => $user) {
             }
 
             if ($showfeedback && $quickfeedback) {
-                $studentshtml .= '<input size="6" type="text" name="feedback_'.$userid.'_'.$item->id.'" value="'. @$grade->feedback . '"/>';
-            } elseif ($showfeedback) { // If quickfeedback is off but showfeedback is on, print an edit feedback icon
+                $studentshtml .= '<input size="6" type="text" name="feedback_'.$userid.'_'.$item->id.'" value="'. s($grade->feedback) . '"/>';
+
+            } else if ($showfeedback) { // If quickfeedback is off but showfeedback is on, print an edit feedback icon
                 if (empty($grade->feedback)) {
                     $icons_html .= grade_get_icons($element, $gtree, array('add_feedback'));
                 } else {
@@ -625,6 +651,7 @@ foreach ($users as $userid => $user) {
 
             $icons_html .= '</div>';
             $studentshtml .= $icons_html;
+
         } else {
             // finalgrades[$userid][$itemid] could be null because of the outer join
             // in this case it's different than a 0
@@ -642,7 +669,11 @@ foreach ($users as $userid => $user) {
                     // no such scale, throw error?
                 }
             } else {
-                $studentshtml .=  get_grade_clean($gradeval);
+                if (is_null($gradeval)) {
+                    $studentshtml .= '-';
+                } else {
+                    $studentshtml .=  get_grade_clean($gradeval);
+                }
             }
         }
 
index 2def407a2e45472c659be10d90d48fa8c1749d8d..371cb844c937ef829f822658a98110c543d64cfe 100644 (file)
@@ -307,7 +307,7 @@ class grade_category extends grade_object {
      *
      * Please note that category grade is either calculated or aggregated - not both at the same time.
      *
-     * This method must be used ONLY from grade_item::update_final_grades(),
+     * This method must be used ONLY from grade_item::regrade_final_grades(),
      * because the calculation must be done in correct order!
      *
      * Steps to follow:
@@ -404,8 +404,8 @@ class grade_category extends grade_object {
             $oldgrade->rawscaleid  = $grade->rawscaleid;
         }
 
-        // locked grades are not regraded
-        if ($grade->is_locked()) {
+        // no need to recalculate locked or overridden grades
+        if ($grade->is_locked() or $grade->is_overridden()) {
             return;
         }
 
index 6c383a015eae88c9e2311ca07c8975d71b888f7a..f205df3625a2e7d9c3d9216324d98e49ca1f49ac 100644 (file)
@@ -125,6 +125,12 @@ class grade_grades extends grade_object {
      */
     var $exported = 0;
 
+    /**
+     * Overridden flag
+     * @var boolean $overridden
+     */
+    var $overridden = 0;
+
     /**
      * Loads the grade_grades_text object linked to this grade (through the intersection of itemid and userid), and
      * saves it as a class variable for this final object.
@@ -162,6 +168,10 @@ class grade_grades extends grade_object {
         return !empty($this->locked) or $this->grade_item->is_locked();
     }
 
+    function is_overridden() {
+        return !empty($this->overridden);
+    }
+
     /**
      * Lock/unlopck this grade.
      *
index 417ca9c1791052c157e83049e6ffb237189c80f7..c72ac89d932d499c4a367fa2a16b3ed71088af70 100644 (file)
@@ -516,12 +516,12 @@ class grade_item extends grade_object {
      * Performs the necessary calculations on the grades_final referenced by this grade_item.
      * Also resets the needsupdate flag once successfully performed.
      *
-     * This function must be used ONLY from lib/gradeslib.php/grade_update_final_grades(),
+     * This function must be used ONLY from lib/gradeslib.php/grade_regrade_final_grades(),
      * because the regrading must be done in correct order!!
      *
      * @return boolean true if ok, error string otherwise
      */
-    function update_final_grades($userid=null) {
+    function regrade_final_grades($userid=null) {
         global $CFG;
 
         // locked grade items already have correct final grades
@@ -547,6 +547,9 @@ class grade_item extends grade_object {
             } else {
                 return "Could not aggregate final grades for category:".$this->id; // TODO: improve and localize
             }
+        } else if ($this->is_manual_item()) {
+            // manual items track only final grades, no raw grades
+            return true;
         }
 
         // normal grade item - just new final grades
@@ -559,7 +562,7 @@ class grade_item extends grade_object {
         if ($rs) {
             if ($rs->RecordCount() > 0) {
                 while ($grade_record = rs_fetch_next_record($rs)) {
-                    if (!empty($grade_record->locked)) {
+                    if (!empty($grade_record->locked) or !empty($grade_record->overridden)) {
                         // this grade is locked - final grade must be ok
                         continue;
                     }
@@ -649,8 +652,6 @@ class grade_item extends grade_object {
             dubugging("Unkown grade type");
             return null;;
         }
-
-
     }
 
     /**
@@ -772,12 +773,20 @@ class grade_item extends grade_object {
         return ($this->itemtype == 'course');
     }
 
+    /**
+     * Is this a manualy graded item?
+     * @return boolean
+     */
+    function is_manual_item() {
+        return ($this->itemtype == 'manual');
+    }
+
     /**
      * Is the grade item normal - associated with module, plugin or something else?
      * @return boolean
      */
     function is_normal_item() {
-        return ($this->itemtype != 'course' and $this->itemtype != 'category');
+        return ($this->itemtype != 'course' and $this->itemtype != 'category' and $this->itemtype != 'manual');
     }
 
     /**
@@ -1038,39 +1047,131 @@ class grade_item extends grade_object {
     }
 
     /**
-     * Updates raw grade value for given user, this is a only way to update raw
-     * grades from external source (module, gradebook, import, etc.),
-     * because it logs the change in history table and deals with final grade recalculation.
-     *
-     * The only exception is category grade item which stores the raw grades directly.
-     * Calculated grades do not use raw grades at all, the rawgrade changes there are not logged too.
+     * Updates final grade value for given user, this is a only way to update final
+     * grades from gradebook and import because it logs the change in history table
+     * and deals with overridden flag. This flag is set to prevent later overriding
+     * from raw grades submitted from modules.
      *
      * @param int $userid the graded user
-     * @param mixed $rawgrade float value of raw grade - false means do not change
+     * @param mixed $finalgrade float value of final grade - false means do not change
      * @param string $howmodified modification source
      * @param string $note optional note
      * @param mixed $feedback teachers feedback as string - false means do not change
      * @param int $feedbackformat
-     * @return mixed grade_grades object if ok, false if error
+     * @return boolean success
      */
-    function update_raw_grade($userid, $rawgrade=false, $source='manual', $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
-        global $CFG, $USER;
-        require_once($CFG->libdir.'/eventslib.php');
+    function update_final_grade($userid, $finalgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
+        global $USER;
 
         if (empty($usermodified)) {
             $usermodified = $USER->id;
         }
 
-        // calculated grades can not be updated
-        if ($this->is_calculated()) {
+        // no grading used or locked
+        if ($this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
             return false;
         }
 
-        // TODO: we should IMO prevent modification of raw grades for course and categroy item too because
-        //       there is no way to prevent overriding of it
+        if (!$grade = grade_grades::fetch(array('itemid'=>$this->id, 'userid'=>$userid))) {
+            $grade = new grade_grades(array('itemid'=>$this->id, 'userid'=>$userid), false);
+        }
 
-        // do not allow grade updates when item locked - this prevents fetching of grade from db
-        if ($this->is_locked()) {
+        $grade->grade_item =& $this; // prevent db fetching of this grade_item
+        $oldgrade = new object();
+        $oldgrade->finalgrade  = $grade->finalgrade;
+        $oldgrade->rawgrade    = $grade->rawgrade;
+        $oldgrade->rawgrademin = $grade->rawgrademin;
+        $oldgrade->rawgrademax = $grade->rawgrademax;
+        $oldgrade->rawscaleid  = $grade->rawscaleid;
+        $oldgrade->overridden  = $grade->overridden;
+
+        if ($grade->is_locked()) {
+            // do not update locked grades at all
+            return false;
+        }
+
+        if (!empty($grade->locktime) and $grade->locktime < time()) {
+            // do not update grades that should be already locked
+            // this does not solve all problems, cron is still needed to recalculate the final grades periodically
+            return false;
+        }
+
+        if ($finalgrade !== false) {
+            $grade->finalgrade = $finalgrade;
+            // if we can update the raw grade, do update it
+            if (!$this->is_normal_item() or $this->plusfactor != 0 or $this->multfactor != 1
+             or !events_is_registered('grade_updated', $this->itemtype.'/'.$this->itemmodule)) {
+                if (!$grade->overridden) {
+                    $grade->overridden = time();
+                }
+            } else {
+                $grade->rawgrade = $finalgrade;
+                // copy current grademin/max and scale
+                $grade->rawgrademin = $this->grademin;
+                $grade->rawgrademax = $this->grademax;
+                $grade->rawscaleid  = $this->scaleid;
+            }
+        }
+
+        if (empty($grade->id)) {
+            $result = (boolean)$grade->insert($source);
+
+        } else if ($grade->finalgrade  !== $oldgrade->finalgrade
+                or $grade->rawgrade    !== $oldgrade->rawgrade
+                or $grade->rawgrademin !== $oldgrade->rawgrademin
+                or $grade->rawgrademax !== $oldgrade->rawgrademax
+                or $grade->rawscaleid  !== $oldgrade->rawscaleid
+                or $grade->overridden  !== $oldgrade->overridden) {
+
+            $result = $grade->update($source);
+        }
+
+        // do we have comment from teacher?
+        if ($result and $feedback !== false) {
+            $result = $grade->update_feedback($feedback, $feedbackformat, $usermodified);
+        }
+
+        if (!$this->needsupdate) {
+            $course_item = grade_item::fetch_course_item($this->courseid);
+            if (!$course_item->needsupdate) {
+                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
+                    $this->force_regrading();
+                }
+            } else {
+                $this->force_regrading();
+            }
+        }
+
+        if ($result and !$grade->overridden) {
+            $this->trigger_raw_updated($grade, $source);
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Updates raw grade value for given user, this is a only way to update raw
+     * grades from external source (modules, etc.),
+     * because it logs the change in history table and deals with final grade recalculation.
+     *
+     * @param int $userid the graded user
+     * @param mixed $rawgrade float value of raw grade - false means do not change
+     * @param string $howmodified modification source
+     * @param string $note optional note
+     * @param mixed $feedback teachers feedback as string - false means do not change
+     * @param int $feedbackformat
+     * @return boolean success
+     */
+    function update_raw_grade($userid, $rawgrade=false, $source=NULL, $note=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null) {
+        global $USER;
+
+        if (empty($usermodified)) {
+            $usermodified = $USER->id;
+        }
+
+        // calculated grades can not be updated; course and category can not be updated  because they are aggregated
+        if ($this->is_calculated() or !$this->is_normal_item() or $this->gradetype == GRADE_TYPE_NONE or $this->is_locked()) {
             return false;
         }
 
@@ -1126,7 +1227,7 @@ class grade_item extends grade_object {
         if (!$this->needsupdate) {
             $course_item = grade_item::fetch_course_item($this->courseid);
             if (!$course_item->needsupdate) {
-                if (!grade_update_final_grades($this->courseid, $userid, $this)) {
+                if (!grade_regrade_final_grades($this->courseid, $userid, $this)) {
                     $this->force_regrading();
                 }
             } else {
@@ -1135,36 +1236,42 @@ class grade_item extends grade_object {
         }
 
         if ($result) {
+            $this->trigger_raw_updated($grade, $source);
+        }
 
-            // trigger grade_updated event notification
-            $eventdata = new object();
-
-            $eventdata->source       = $source;
-            $eventdata->itemid       = $this->id;
-            $eventdata->courseid     = $this->courseid;
-            $eventdata->itemtype     = $this->itemtype;
-            $eventdata->itemmodule   = $this->itemmodule;
-            $eventdata->iteminstance = $this->iteminstance;
-            $eventdata->itemnumber   = $this->itemnumber;
-            $eventdata->idnumber     = $this->idnumber;
-            $eventdata->userid       = $grade->userid;
-            $eventdata->rawgrade     = $grade->rawgrade;
-
-            // load existing text annotation
-            if ($grade_text = $grade->load_text()) {
-                $eventdata->feedback          = $grade_text->feedback;
-                $eventdata->feedbackformat    = $grade_text->feedbackformat;
-                $eventdata->information       = $grade_text->information;
-                $eventdata->informationformat = $grade_text->informationformat;
-            }
-
-            events_trigger('grade_updated', $eventdata);
+        return $result;
+    }
 
-            return $grade;
+    /**
+     * Internal function used by update_final/raw_grade() only.
+     */
+    function trigger_raw_updated($grade, $source) {
+        global $CFG;
+        require_once($CFG->libdir.'/eventslib.php');
 
-        } else {
-            return false;
-        }
+        // trigger grade_updated event notification
+        $eventdata = new object();
+
+        $eventdata->source       = $source;
+        $eventdata->itemid       = $this->id;
+        $eventdata->courseid     = $this->courseid;
+        $eventdata->itemtype     = $this->itemtype;
+        $eventdata->itemmodule   = $this->itemmodule;
+        $eventdata->iteminstance = $this->iteminstance;
+        $eventdata->itemnumber   = $this->itemnumber;
+        $eventdata->idnumber     = $this->idnumber;
+        $eventdata->userid       = $grade->userid;
+        $eventdata->rawgrade     = $grade->rawgrade;
+
+        // load existing text annotation
+        if ($grade_text = $grade->load_text()) {
+            $eventdata->feedback          = $grade_text->feedback;
+            $eventdata->feedbackformat    = $grade_text->feedbackformat;
+            $eventdata->information       = $grade_text->information;
+            $eventdata->informationformat = $grade_text->informationformat;
+        }
+
+        events_trigger('grade_updated', $eventdata);
     }
 
     /**
@@ -1274,8 +1381,8 @@ class grade_item extends grade_object {
             $oldgrade->rawgrade    = $grade->rawgrade;
         }
 
-        // no need to recalculate locked grades
-        if ($grade->is_locked()) {
+        // no need to recalculate locked or overridden grades
+        if ($grade->is_locked() or $grade->is_overridden()) {
             return;
         }
 
index 9c4156886dd8ace4e4c0a22a94d8b5a1ef27bead..73da1e8b1857405fc4b8dd7c233c6c323e4ded1a 100644 (file)
@@ -311,7 +311,7 @@ function grade_force_full_regrading($courseid) {
  * @param object $updated_item the item in which
  * @return boolean true if ok, array of errors if problems found (item id is used as key)
  */
-function grade_update_final_grades($courseid, $userid=null, $updated_item=null) {
+function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null) {
 
     $course_item = grade_item::fetch_course_item($courseid);
 
@@ -374,7 +374,7 @@ function grade_update_final_grades($courseid, $userid=null, $updated_item=null)
 
             //oki - let's update, calculate or aggregate :-)
             if ($doupdate) {
-                $result = $grade_item->update_final_grades($userid);
+                $result = $grade_item->regrade_final_grades($userid);
 
                 if ($result === true) {
                     $grade_item->regrading_finished();
index c61fb7460aa55217ffbcf1eb66a1b69a46d1ddef..933db6d2774060bc32aca9ca85a8abdb190fe569 100755 (executable)
@@ -287,10 +287,10 @@ class grade_item_test extends grade_test {
     /**
      * Test update of all final grades
      */
-    function test_grade_item_update_final_grades() {
+    function test_grade_item_regrade_final_grades() {
         $grade_item = new grade_item($this->grade_items[0]);
-        $this->assertTrue(method_exists($grade_item, 'update_final_grades'));
-        $this->assertEqual(true, $grade_item->update_final_grades());
+        $this->assertTrue(method_exists($grade_item, 'regrade_final_grades'));
+        $this->assertEqual(true, $grade_item->regrade_final_grades());
         //TODO: add more tests
     }
 
index e1ffefd12e6359fccec06a4df754d2dca11a987e..c8fc153516087e77cc9f63999c9ba58836259af8 100644 (file)
@@ -712,6 +712,10 @@ body#grade-index .grades .r1 {
   background-color: #dddddd;
 }
 
+#grade-report td.overridden {
+  background-color: #efefef;
+}
+
 /***
  *** Login
  ***/