Example #1
0
    /**
     * Given a list of all assessments of a single submission, updates the grading grades in database
     *
     * @param array $assessments of stdclass (->assessmentid ->assessmentweight ->reviewerid ->gradinggrade ->submissionid ->dimensionid ->grade)
     * @param array $diminfo of stdclass (->id ->weight ->max ->min)
     * @param stdClass grading evaluation settings
     * @return void
     */
    protected function process_assessments(array $assessments, array $diminfo, stdclass $settings) {
        global $DB;

        if (empty($assessments)) {
            return;
        }

        // reindex the passed flat structure to be indexed by assessmentid
        $assessments = $this->prepare_data_from_recordset($assessments);

        // normalize the dimension grades to the interval 0 - 100
        $assessments = $this->normalize_grades($assessments, $diminfo);

        // get a hypothetical average assessment
        $average = $this->average_assessment($assessments);

        // calculate variance of dimension grades
        $variances = $this->weighted_variance($assessments);
        foreach ($variances as $dimid => $variance) {
            $diminfo[$dimid]->variance = $variance;
        }

        // for every assessment, calculate its distance from the average one
        $distances = array();
        foreach ($assessments as $asid => $assessment) {
            $distances[$asid] = $this->assessments_distance($assessment, $average, $diminfo, $settings);
        }

        // identify the best assessments - that is those with the shortest distance from the best assessment
        $bestids = array_keys($distances, min($distances));

        // for every assessment, calculate its distance from the nearest best assessment
        $distances = array();
        foreach ($bestids as $bestid) {
            $best = $assessments[$bestid];
            foreach ($assessments as $asid => $assessment) {
                $d = $this->assessments_distance($assessment, $best, $diminfo, $settings);
                if (!is_null($d) and (!isset($distances[$asid]) or $d < $distances[$asid])) {
                    $distances[$asid] = $d;
                }
            }
        }

        // calculate the grading grade
        foreach ($distances as $asid => $distance) {
            $gradinggrade = (100 - $distance);
            if ($gradinggrade < 0) {
                $gradinggrade = 0;
            }
            if ($gradinggrade > 100) {
                $gradinggrade = 100;
            }
            $grades[$asid] = grade_floatval($gradinggrade);
        }

        // if the new grading grade differs from the one stored in database, update it
        // we do not use set_field() here because we want to pass $bulk param
        foreach ($grades as $assessmentid => $grade) {
            if (grade_floats_different($grade, $assessments[$assessmentid]->gradinggrade)) {
                // the value has changed
                $record = new stdclass();
                $record->id = $assessmentid;
                $record->gradinggrade = grade_floatval($grade);
                // do not set timemodified here, it contains the timestamp of when the form was
                // saved by the peer reviewer, not when it was aggregated
                $DB->update_record('workshop_assessments', $record, true);  // bulk operations expected
            }
        }

        // done. easy, heh? ;-)
    }
Example #2
0
/**
 * Submit new or update grade; update/create grade_item definition. Grade must have userid specified,
 * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded'.
 * Missing property or key means does not change the existing value.
 *
 * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax',
 * 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones.
 *
 * Manual, course or category items can not be updated by this function.
 *
 * @category grade
 * @param string $source Source of the grade such as 'mod/assignment'
 * @param int    $courseid ID of course
 * @param string $itemtype Type of grade item. For example, mod or block
 * @param string $itemmodule More specific then $itemtype. For example, assignment or forum. May be NULL for some item types
 * @param int    $iteminstance Instance ID of graded item
 * @param int    $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user
 * @param mixed  $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only
 * @param mixed  $itemdetails Object or array describing the grading item, NULL if no change
 * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED
 */
function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance, $itemnumber, $grades = NULL, $itemdetails = NULL)
{
    global $USER, $CFG, $DB;
    // only following grade_item properties can be changed in this function
    $allowed = array('itemname', 'idnumber', 'gradetype', 'grademax', 'grademin', 'scaleid', 'multfactor', 'plusfactor', 'deleted', 'hidden');
    // list of 10,5 numeric fields
    $floats = array('grademin', 'grademax', 'multfactor', 'plusfactor');
    // grade item identification
    $params = compact('courseid', 'itemtype', 'itemmodule', 'iteminstance', 'itemnumber');
    if (is_null($courseid) or is_null($itemtype)) {
        debugging('Missing courseid or itemtype');
        return GRADE_UPDATE_FAILED;
    }
    if (!($grade_items = grade_item::fetch_all($params))) {
        // create a new one
        $grade_item = false;
    } else {
        if (count($grade_items) == 1) {
            $grade_item = reset($grade_items);
            unset($grade_items);
            //release memory
        } else {
            debugging('Found more than one grade item');
            return GRADE_UPDATE_MULTIPLE;
        }
    }
    if (!empty($itemdetails['deleted'])) {
        if ($grade_item) {
            if ($grade_item->delete($source)) {
                return GRADE_UPDATE_OK;
            } else {
                return GRADE_UPDATE_FAILED;
            }
        }
        return GRADE_UPDATE_OK;
    }
    /// Create or update the grade_item if needed
    if (!$grade_item) {
        if ($itemdetails) {
            $itemdetails = (array) $itemdetails;
            // grademin and grademax ignored when scale specified
            if (array_key_exists('scaleid', $itemdetails)) {
                if ($itemdetails['scaleid']) {
                    unset($itemdetails['grademin']);
                    unset($itemdetails['grademax']);
                }
            }
            foreach ($itemdetails as $k => $v) {
                if (!in_array($k, $allowed)) {
                    // ignore it
                    continue;
                }
                if ($k == 'gradetype' and $v == GRADE_TYPE_NONE) {
                    // no grade item needed!
                    return GRADE_UPDATE_OK;
                }
                $params[$k] = $v;
            }
        }
        $grade_item = new grade_item($params);
        $grade_item->insert();
    } else {
        if ($grade_item->is_locked()) {
            // no notice() here, test returned value instead!
            return GRADE_UPDATE_ITEM_LOCKED;
        }
        if ($itemdetails) {
            $itemdetails = (array) $itemdetails;
            $update = false;
            foreach ($itemdetails as $k => $v) {
                if (!in_array($k, $allowed)) {
                    // ignore it
                    continue;
                }
                if (in_array($k, $floats)) {
                    if (grade_floats_different($grade_item->{$k}, $v)) {
                        $grade_item->{$k} = $v;
                        $update = true;
                    }
                } else {
                    if ($grade_item->{$k} != $v) {
                        $grade_item->{$k} = $v;
                        $update = true;
                    }
                }
            }
            if ($update) {
                $grade_item->update();
            }
        }
    }
    /// reset grades if requested
    if (!empty($itemdetails['reset'])) {
        $grade_item->delete_all_grades('reset');
        return GRADE_UPDATE_OK;
    }
    /// Some extra checks
    // do we use grading?
    if ($grade_item->gradetype == GRADE_TYPE_NONE) {
        return GRADE_UPDATE_OK;
    }
    // no grade submitted
    if (empty($grades)) {
        return GRADE_UPDATE_OK;
    }
    /// Finally start processing of grades
    if (is_object($grades)) {
        $grades = array($grades->userid => $grades);
    } else {
        if (array_key_exists('userid', $grades)) {
            $grades = array($grades['userid'] => $grades);
        }
    }
    /// normalize and verify grade array
    foreach ($grades as $k => $g) {
        if (!is_array($g)) {
            $g = (array) $g;
            $grades[$k] = $g;
        }
        if (empty($g['userid']) or $k != $g['userid']) {
            debugging('Incorrect grade array index, must be user id! Grade ignored.');
            unset($grades[$k]);
        }
    }
    if (empty($grades)) {
        return GRADE_UPDATE_FAILED;
    }
    $count = count($grades);
    if ($count > 0 and $count < 200) {
        list($uids, $params) = $DB->get_in_or_equal(array_keys($grades), SQL_PARAMS_NAMED, $start = 'uid');
        $params['gid'] = $grade_item->id;
        $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid AND userid {$uids}";
    } else {
        $sql = "SELECT * FROM {grade_grades} WHERE itemid = :gid";
        $params = array('gid' => $grade_item->id);
    }
    $rs = $DB->get_recordset_sql($sql, $params);
    $failed = false;
    while (count($grades) > 0) {
        $grade_grade = null;
        $grade = null;
        foreach ($rs as $gd) {
            $userid = $gd->userid;
            if (!isset($grades[$userid])) {
                // this grade not requested, continue
                continue;
            }
            // existing grade requested
            $grade = $grades[$userid];
            $grade_grade = new grade_grade($gd, false);
            unset($grades[$userid]);
            break;
        }
        if (is_null($grade_grade)) {
            if (count($grades) == 0) {
                // no more grades to process
                break;
            }
            $grade = reset($grades);
            $userid = $grade['userid'];
            $grade_grade = new grade_grade(array('itemid' => $grade_item->id, 'userid' => $userid), false);
            $grade_grade->load_optional_fields();
            // add feedback and info too
            unset($grades[$userid]);
        }
        $rawgrade = false;
        $feedback = false;
        $feedbackformat = FORMAT_MOODLE;
        $usermodified = $USER->id;
        $datesubmitted = null;
        $dategraded = null;
        if (array_key_exists('rawgrade', $grade)) {
            $rawgrade = $grade['rawgrade'];
        }
        if (array_key_exists('feedback', $grade)) {
            $feedback = $grade['feedback'];
        }
        if (array_key_exists('feedbackformat', $grade)) {
            $feedbackformat = $grade['feedbackformat'];
        }
        if (array_key_exists('usermodified', $grade)) {
            $usermodified = $grade['usermodified'];
        }
        if (array_key_exists('datesubmitted', $grade)) {
            $datesubmitted = $grade['datesubmitted'];
        }
        if (array_key_exists('dategraded', $grade)) {
            $dategraded = $grade['dategraded'];
        }
        // update or insert the grade
        if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) {
            $failed = true;
        }
    }
    if ($rs) {
        $rs->close();
    }
    if (!$failed) {
        return GRADE_UPDATE_OK;
    } else {
        return GRADE_UPDATE_FAILED;
    }
}
 /**
  * Returns true if the grade's value is superior or equal to the grade item's gradepass value, false otherwise.
  *
  * @param grade_item $grade_item An optional grade_item of which gradepass value we can use, saves having to load the grade_grade's grade_item
  * @return bool
  */
 public function is_passed($grade_item = null)
 {
     if (empty($grade_item)) {
         if (!isset($this->grade_item)) {
             $this->load_grade_item();
         }
     } else {
         $this->grade_item = $grade_item;
         $this->itemid = $grade_item->id;
     }
     // Return null if finalgrade is null
     if (is_null($this->finalgrade)) {
         return null;
     }
     // Return null if gradepass == grademin, gradepass is null, or grade item is a scale and gradepass is 0.
     if (is_null($this->grade_item->gradepass)) {
         return null;
     } else {
         if ($this->grade_item->gradepass == $this->grade_item->grademin) {
             return null;
         } else {
             if ($this->grade_item->gradetype == GRADE_TYPE_SCALE && !grade_floats_different($this->grade_item->gradepass, 0.0)) {
                 return null;
             }
         }
     }
     return $this->finalgrade >= $this->grade_item->gradepass;
 }
Example #4
0
 /**
  * internal function - does the final grade calculation
  */
 function use_formula($userid, $params, $useditems, $oldgrade)
 {
     if (empty($userid)) {
         return true;
     }
     // add missing final grade values
     // not graded (null) is counted as 0 - the spreadsheet way
     foreach ($useditems as $gi) {
         if (!array_key_exists('gi' . $gi, $params)) {
             $params['gi' . $gi] = 0;
         } else {
             $params['gi' . $gi] = (double) $params['gi' . $gi];
         }
     }
     // can not use own final grade during calculation
     unset($params['gi' . $this->id]);
     // insert final grade - will be needed later anyway
     if ($oldgrade) {
         $oldfinalgrade = $oldgrade->finalgrade;
         $grade = new grade_grade($oldgrade, false);
         // fetching from db is not needed
         $grade->grade_item =& $this;
     } else {
         $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false);
         $grade->grade_item =& $this;
         $grade->insert('system');
         $oldfinalgrade = null;
     }
     // no need to recalculate locked or overridden grades
     if ($grade->is_locked() or $grade->is_overridden()) {
         return true;
     }
     // do the calculation
     $this->formula->set_params($params);
     $result = $this->formula->evaluate();
     if ($result === false) {
         $grade->finalgrade = null;
     } else {
         // normalize
         $result = bounded_number($this->grademin, $result, $this->grademax);
         if ($this->gradetype == GRADE_TYPE_SCALE) {
             $result = round($result + 1.0E-5);
             // round scales upwards
         }
         $grade->finalgrade = $result;
     }
     // update in db if changed
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
         $grade->update('compute');
     }
     if ($result !== false) {
         //lock grade if needed
     }
     if ($result === false) {
         return false;
     } else {
         return true;
     }
 }
 /**
  * internal function for category grades aggregation
  *
  * @param int $userid
  * @param array $items
  * @param array $grade_values
  * @param object $oldgrade
  * @param bool $excluded
  * @return boolean (just plain return;)
  */
 function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded, $grade_max, $grade_min)
 {
     global $CFG;
     if (empty($userid)) {
         //ignore first call
         return;
     }
     if ($oldgrade) {
         $oldfinalgrade = $oldgrade->finalgrade;
         $grade = new grade_grade_local($oldgrade, false);
         $grade->grade_item =& $this->grade_item;
     } else {
         // insert final grade - it will be needed later anyway
         $grade = new grade_grade_local(array('itemid' => $this->grade_item->id, 'userid' => $userid), false);
         $grade->grade_item =& $this->grade_item;
         $grade->insert('system');
         $oldfinalgrade = null;
     }
     // no need to recalculate locked or overridden grades
     if ($grade->is_locked() or $grade->is_overridden()) {
         return;
     }
     // can not use own final category grade in calculation
     unset($grade_values[$this->grade_item->id]);
     /// sum is a special aggregation types - it adjusts the min max, does not use relative values
     // HACK: Bob Puffer 12/8/09 to allow for correct summing of point totals for all aggregation methods
     if ($this->aggregation == GRADE_AGGREGATE_SUM) {
         $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded);
         return;
     }
     // CONSIDER REINSTATING THIS IF IT DOESN'T WORK OUT TO REMOVE IT
     // sum all aggregation methods
     //        $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded); // END OF HACK
     // if no grades calculation possible or grading not allowed clear final grade
     if (empty($grade_values) or empty($items) or $this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE) {
         $grade->finalgrade = null;
         if (!is_null($oldfinalgrade)) {
             $grade->update('aggregation');
         }
         return;
     }
     /// normalize the grades first - all will have value 0...1
     // ungraded items are not used in aggregation
     foreach ($grade_values as $itemid => $v) {
         if (is_null($v)) {
             // null means no grade
             unset($grade_values[$itemid]);
             continue;
         } else {
             if (in_array($itemid, $excluded)) {
                 unset($grade_values[$itemid]);
                 continue;
             }
         }
         //            $grade_values[$itemid] = grade_grade::standardise_score($v, $items[$itemid]->grademin, $items[$itemid]->grademax, 0, 1);
         $items[$itemid]->grademax = $grade_max[$itemid];
         $grade_values[$itemid] = grade_grade::standardise_score($v, $grade_min[$itemid], $grade_max[$itemid], 0, 1);
     }
     // use min grade if grade missing for these types
     if (!$this->aggregateonlygraded) {
         foreach ($items as $itemid => $value) {
             if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
                 $grade_values[$itemid] = 0;
             }
         }
     }
     // limit and sort
     $this->apply_limit_rules($grade_values, $items);
     asort($grade_values, SORT_NUMERIC);
     // HACK 10/29/09 Bob Puffer to allow accurate computation of category maxgrade
     // has to be done after any dropped grades are dropped
     $cat_max = 0;
     // END OF HACK
     foreach ($grade_values as $itemid => $v) {
         if ($items[$itemid]->aggregationcoef == 1 and $this->aggregation != GRADE_AGGREGATE_WEIGHTED_MEAN) {
         } else {
             if ($items[$itemid]->itemtype == 'category') {
                 //                $gradegradesrec = new grade_grade_local(array('itemid'=>$itemid, 'userid'=>$userid), true);
                 //              if (isset($gradegradesrec->itemid->itemtype) AND $gradegradesrec->itemid->itemtype == 'category') {
                 //                $cat_max += $gradegradesrec->rawgrademax;
                 $cat_max += $grade_max[$itemid];
             } else {
                 //                $cat_max += $items[$itemid]->grademax;
                 //                    $cat_max += $gradegradesrec->itemid->grademax;
                 $cat_max += $grade_max[$itemid];
                 //                }
             }
         }
     }
     // END OF HACK
     // let's see we have still enough grades to do any statistics
     if (count($grade_values) == 0) {
         // not enough attempts yet
         $grade->finalgrade = null;
         if (!is_null($oldfinalgrade)) {
             $grade->update('aggregation');
         }
         return;
     }
     // do the maths
     // HACK: Bob Puffer 10/29/09 to allow proper totalling of points for the category
     //        $agg_grade = $this->aggregate_values($grade_values, $items);
     // recalculate the grade back to requested range
     //        $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
     $this->grade_item->grademax = $cat_max;
     $oldmaxgrade = $grade->rawgrademax;
     $grade->rawgrademax = $cat_max;
     // HACK we don't want to call the aggregate_values function
     if ($this->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) {
         $weightsum = 0;
         $sum = null;
         foreach ($grade_values as $itemid => $grade_value) {
             $weight = $grade_max[$itemid] - $grade_min[$itemid];
             //                    $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
             if ($weight <= 0) {
                 continue;
             }
             $sum += $weight * $grade_value;
         }
         if ($weightsum == 0) {
             $finalgrade = $sum;
             // only extra credits
         } else {
             $finalgrade = $sum / $weightsum;
         }
     } else {
         $agg_grade = $this->aggregate_values($grade_values, $items);
         // recalculate the grade back to requested range
         $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
     }
     // END OF HACK
     $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade);
     // update in db if changed
     // HACK to update category maxes in the db if they change
     //        if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade) or grade_floats_different($grade->rawgrademax, $oldmaxgrade)) {
         $grade->update('aggregation');
     }
     return;
 }
Example #6
0
 /**
  * internal function for category grades summing
  *
  * @param object $grade
  * @param int $userid
  * @param float $oldfinalgrade
  * @param array $items
  * @param array $grade_values
  * @param bool $excluded
  * @return boolean (just plain return;)
  */
 function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded)
 {
     // ungraded and exluded items are not used in aggregation
     foreach ($grade_values as $itemid => $v) {
         if (is_null($v)) {
             unset($grade_values[$itemid]);
         } else {
             if (in_array($itemid, $excluded)) {
                 unset($grade_values[$itemid]);
             }
         }
     }
     // use 0 if grade missing, droplow used and aggregating all items
     if (!$this->aggregateonlygraded and !empty($this->droplow)) {
         foreach ($items as $itemid => $value) {
             if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
                 $grade_values[$itemid] = 0;
             }
         }
     }
     $max = 0;
     //find max grade
     foreach ($items as $item) {
         if ($item->aggregationcoef > 0) {
             // extra credit from this activity - does not affect total
             continue;
         }
         if ($item->gradetype == GRADE_TYPE_VALUE) {
             $max += $item->grademax;
         } else {
             if ($item->gradetype == GRADE_TYPE_SCALE) {
                 $max += $item->grademax - 1;
                 // scales min is 1
             }
         }
     }
     if ($this->grade_item->grademax != $max or $this->grade_item->grademin != 0 or $this->grade_item->gradetype != GRADE_TYPE_VALUE) {
         $this->grade_item->grademax = $max;
         $this->grade_item->grademin = 0;
         $this->grade_item->gradetype = GRADE_TYPE_VALUE;
         $this->grade_item->update('aggregation');
     }
     $this->apply_limit_rules($grade_values);
     $sum = array_sum($grade_values);
     $grade->finalgrade = bounded_number($this->grade_item->grademin, $sum, $this->grade_item->grademax);
     // update in db if changed
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
         $grade->update('aggregation');
     }
     return;
 }
Example #7
0
 /**
  * Internal function that does the final grade calculation
  *
  * @param int $userid The user ID
  * @param array $params An array of grade items of the form {'gi'.$itemid]} => $finalgrade
  * @param array $useditems An array of grade item IDs that this grade item depends on plus its own ID
  * @param grade_grade $oldgrade A grade_grade instance containing the old values from the database
  * @return bool False if an error occurred
  */
 public function use_formula($userid, $params, $useditems, $oldgrade)
 {
     if (empty($userid)) {
         return true;
     }
     // add missing final grade values
     // not graded (null) is counted as 0 - the spreadsheet way
     $allinputsnull = true;
     foreach ($useditems as $gi) {
         if (!array_key_exists('gi' . $gi, $params) || is_null($params['gi' . $gi])) {
             $params['gi' . $gi] = 0;
         } else {
             $params['gi' . $gi] = (double) $params['gi' . $gi];
             if ($gi != $this->id) {
                 $allinputsnull = false;
             }
         }
     }
     // can not use own final grade during calculation
     unset($params['gi' . $this->id]);
     // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they
     // wish to update the grades.
     $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->courseid);
     $rawminandmaxchanged = false;
     // insert final grade - will be needed later anyway
     if ($oldgrade) {
         // Only run through this code if the gradebook isn't frozen.
         if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) {
             // Do nothing.
         } else {
             // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the
             // grade_item grade maximum and minimum respectively.
             if ($oldgrade->rawgrademax != $this->grademax || $oldgrade->rawgrademin != $this->grademin) {
                 $rawminandmaxchanged = true;
                 $oldgrade->rawgrademax = $this->grademax;
                 $oldgrade->rawgrademin = $this->grademin;
             }
         }
         $oldfinalgrade = $oldgrade->finalgrade;
         $grade = new grade_grade($oldgrade, false);
         // fetching from db is not needed
         $grade->grade_item =& $this;
     } else {
         $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false);
         $grade->grade_item =& $this;
         $rawminandmaxchanged = false;
         if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) {
             // Do nothing.
         } else {
             // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the
             // grade_item grade maximum and minimum respectively.
             $rawminandmaxchanged = true;
             $grade->rawgrademax = $this->grademax;
             $grade->rawgrademin = $this->grademin;
         }
         $grade->insert('system');
         $oldfinalgrade = null;
     }
     // no need to recalculate locked or overridden grades
     if ($grade->is_locked() or $grade->is_overridden()) {
         return true;
     }
     if ($allinputsnull) {
         $grade->finalgrade = null;
         $result = true;
     } else {
         // do the calculation
         $this->formula->set_params($params);
         $result = $this->formula->evaluate();
         if ($result === false) {
             $grade->finalgrade = null;
         } else {
             // normalize
             $grade->finalgrade = $this->bounded_grade($result);
         }
     }
     // Only run through this code if the gradebook isn't frozen.
     if ($gradebookcalculationsfreeze && (int) $gradebookcalculationsfreeze <= 20150627) {
         // Update in db if changed.
         if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
             $grade->timemodified = time();
             $success = $grade->update('compute');
             // If successful trigger a user_graded event.
             if ($success) {
                 \core\event\user_graded::create_from_grade($grade)->trigger();
             }
         }
     } else {
         // Update in db if changed.
         if (grade_floats_different($grade->finalgrade, $oldfinalgrade) || $rawminandmaxchanged) {
             $grade->timemodified = time();
             $success = $grade->update('compute');
             // If successful trigger a user_graded event.
             if ($success) {
                 \core\event\user_graded::create_from_grade($grade)->trigger();
             }
         }
     }
     if ($result !== false) {
         //lock grade if needed
     }
     if ($result === false) {
         return false;
     } else {
         return true;
     }
 }
Example #8
0
    /**
     * Given an array of all assessments done by a single reviewer, calculates the final grading grade
     *
     * This calculates the simple mean of the passed grading grades. If, however, the grading grade
     * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
     *
     * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
     * @return void
     */
    protected function aggregate_grading_grades_process(array $assessments) {
        global $DB;

        $reviewerid = null; // the id of the reviewer being processed
        $current    = null; // the gradinggrade currently saved in database
        $finalgrade = null; // the new grade to be calculated
        $agid       = null; // aggregation id
        $sumgrades  = 0;
        $count      = 0;

        foreach ($assessments as $assessment) {
            if (is_null($reviewerid)) {
                // the id is the same in all records, fetch it during the first loop cycle
                $reviewerid = $assessment->reviewerid;
            }
            if (is_null($agid)) {
                // the id is the same in all records, fetch it during the first loop cycle
                $agid = $assessment->aggregationid;
            }
            if (is_null($current)) {
                // the currently saved grade is the same in all records, fetch it during the first loop cycle
                $current = $assessment->aggregatedgrade;
            }
            if (!is_null($assessment->gradinggradeover)) {
                // the grading grade for this assessment is overridden by a teacher
                $sumgrades += $assessment->gradinggradeover;
                $count++;
            } else {
                if (!is_null($assessment->gradinggrade)) {
                    $sumgrades += $assessment->gradinggrade;
                    $count++;
                }
            }
        }
        if ($count > 0) {
            $finalgrade = grade_floatval($sumgrades / $count);
        }
        // check if the new final grade differs from the one stored in the database
        if (grade_floats_different($finalgrade, $current)) {
            // we need to save new calculation into the database
            if (is_null($agid)) {
                // no aggregation record yet
                $record = new stdclass();
                $record->workshopid = $this->id;
                $record->userid = $reviewerid;
                $record->gradinggrade = $finalgrade;
                $record->timegraded = time();
                $DB->insert_record('workshop_aggregations', $record);
            } else {
                $record = new stdclass();
                $record->id = $agid;
                $record->gradinggrade = $finalgrade;
                $record->timegraded = time();
                $DB->update_record('workshop_aggregations', $record);
            }
        }
    }
Example #9
0
 /**
  * Internal function for grade category grade aggregation
  *
  * @param int    $userid The User ID
  * @param array  $items Grade items
  * @param array  $grade_values Array of grade values
  * @param object $oldgrade Old grade
  * @param array  $excluded Excluded
  * @param array  $grademinoverrides User specific grademin values if different to the grade_item grademin (key is itemid)
  * @param array  $grademaxoverrides User specific grademax values if different to the grade_item grademax (key is itemid)
  */
 private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded, $grademinoverrides, $grademaxoverrides)
 {
     global $CFG, $DB;
     // Remember these so we can set flags on them to describe how they were used in the aggregation.
     $novalue = array();
     $dropped = array();
     $extracredit = array();
     $usedweights = array();
     if (empty($userid)) {
         //ignore first call
         return;
     }
     if ($oldgrade) {
         $oldfinalgrade = $oldgrade->finalgrade;
         $grade = new grade_grade($oldgrade, false);
         $grade->grade_item =& $this->grade_item;
     } else {
         // insert final grade - it will be needed later anyway
         $grade = new grade_grade(array('itemid' => $this->grade_item->id, 'userid' => $userid), false);
         $grade->grade_item =& $this->grade_item;
         $grade->insert('system');
         $oldfinalgrade = null;
     }
     // no need to recalculate locked or overridden grades
     if ($grade->is_locked() or $grade->is_overridden()) {
         return;
     }
     // can not use own final category grade in calculation
     unset($grade_values[$this->grade_item->id]);
     // Make sure a grade_grade exists for every grade_item.
     // We need to do this so we can set the aggregationstatus
     // with a set_field call instead of checking if each one exists and creating/updating.
     if (!empty($items)) {
         list($ggsql, $params) = $DB->get_in_or_equal(array_keys($items), SQL_PARAMS_NAMED, 'g');
         $params['userid'] = $userid;
         $sql = "SELECT itemid\n                      FROM {grade_grades}\n                     WHERE itemid {$ggsql} AND userid = :userid";
         $existingitems = $DB->get_records_sql($sql, $params);
         $notexisting = array_diff(array_keys($items), array_keys($existingitems));
         foreach ($notexisting as $itemid) {
             $gradeitem = $items[$itemid];
             $gradegrade = new grade_grade(array('itemid' => $itemid, 'userid' => $userid, 'rawgrademin' => $gradeitem->grademin, 'rawgrademax' => $gradeitem->grademax), false);
             $gradegrade->grade_item = $gradeitem;
             $gradegrade->insert('system');
         }
     }
     // if no grades calculation possible or grading not allowed clear final grade
     if (empty($grade_values) or empty($items) or $this->grade_item->gradetype != GRADE_TYPE_VALUE and $this->grade_item->gradetype != GRADE_TYPE_SCALE) {
         $grade->finalgrade = null;
         if (!is_null($oldfinalgrade)) {
             $success = $grade->update('aggregation');
             // If successful trigger a user_graded event.
             if ($success) {
                 \core\event\user_graded::create_from_grade($grade)->trigger();
             }
         }
         $dropped = $grade_values;
         $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
         return;
     }
     // Normalize the grades first - all will have value 0...1
     // ungraded items are not used in aggregation.
     foreach ($grade_values as $itemid => $v) {
         if (is_null($v)) {
             // If null, it means no grade.
             if ($this->aggregateonlygraded) {
                 unset($grade_values[$itemid]);
                 // Mark this item as "excluded empty" because it has no grade.
                 $novalue[$itemid] = 0;
                 continue;
             }
         }
         if (in_array($itemid, $excluded)) {
             unset($grade_values[$itemid]);
             $dropped[$itemid] = 0;
             continue;
         }
         // Check for user specific grade min/max overrides.
         $usergrademin = $items[$itemid]->grademin;
         $usergrademax = $items[$itemid]->grademax;
         if (isset($grademinoverrides[$itemid])) {
             $usergrademin = $grademinoverrides[$itemid];
         }
         if (isset($grademaxoverrides[$itemid])) {
             $usergrademax = $grademaxoverrides[$itemid];
         }
         if ($this->aggregation == GRADE_AGGREGATE_SUM) {
             // Assume that the grademin is 0 when standardising the score, to preserve negative grades.
             $grade_values[$itemid] = grade_grade::standardise_score($v, 0, $usergrademax, 0, 1);
         } else {
             $grade_values[$itemid] = grade_grade::standardise_score($v, $usergrademin, $usergrademax, 0, 1);
         }
     }
     // For items with no value, and not excluded - either set their grade to 0 or exclude them.
     foreach ($items as $itemid => $value) {
         if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
             if (!$this->aggregateonlygraded) {
                 $grade_values[$itemid] = 0;
             } else {
                 // We are specifically marking these items as "excluded empty".
                 $novalue[$itemid] = 0;
             }
         }
     }
     // limit and sort
     $allvalues = $grade_values;
     if ($this->can_apply_limit_rules()) {
         $this->apply_limit_rules($grade_values, $items);
     }
     $moredropped = array_diff($allvalues, $grade_values);
     foreach ($moredropped as $drop => $unused) {
         $dropped[$drop] = 0;
     }
     foreach ($grade_values as $itemid => $val) {
         if (self::is_extracredit_used() && $items[$itemid]->aggregationcoef > 0) {
             $extracredit[$itemid] = 0;
         }
     }
     asort($grade_values, SORT_NUMERIC);
     // let's see we have still enough grades to do any statistics
     if (count($grade_values) == 0) {
         // not enough attempts yet
         $grade->finalgrade = null;
         if (!is_null($oldfinalgrade)) {
             $success = $grade->update('aggregation');
             // If successful trigger a user_graded event.
             if ($success) {
                 \core\event\user_graded::create_from_grade($grade)->trigger();
             }
         }
         $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
         return;
     }
     // do the maths
     $result = $this->aggregate_values_and_adjust_bounds($grade_values, $items, $usedweights, $grademinoverrides, $grademaxoverrides);
     $agg_grade = $result['grade'];
     // Set the actual grademin and max to bind the grade properly.
     $this->grade_item->grademin = $result['grademin'];
     $this->grade_item->grademax = $result['grademax'];
     if ($this->aggregation == GRADE_AGGREGATE_SUM) {
         // The natural aggregation always displays the range as coming from 0 for categories.
         // However, when we bind the grade we allow for negative values.
         $result['grademin'] = 0;
     }
     // Recalculate the grade back to requested range.
     $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $result['grademin'], $result['grademax']);
     $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade);
     $oldrawgrademin = $grade->rawgrademin;
     $oldrawgrademax = $grade->rawgrademax;
     $grade->rawgrademin = $result['grademin'];
     $grade->rawgrademax = $result['grademax'];
     // Update in db if changed.
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade) || grade_floats_different($grade->rawgrademax, $oldrawgrademax) || grade_floats_different($grade->rawgrademin, $oldrawgrademin)) {
         $success = $grade->update('aggregation');
         // If successful trigger a user_graded event.
         if ($success) {
             \core\event\user_graded::create_from_grade($grade)->trigger();
         }
     }
     $this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
     return;
 }
Example #10
0
 /**
  * Called by HTML_QuickForm whenever form event is made on this element.
  *
  * @param string $event Name of event
  * @param mixed $arg event arguments
  * @param moodleform $caller calling object
  * @return mixed
  */
 public function onQuickFormEvent($event, $arg, &$caller)
 {
     switch ($event) {
         case 'createElement':
             // The first argument is the name.
             $name = $arg[0];
             // Set disable actions.
             $caller->disabledIf($name . '[modgrade_scale]', $name . '[modgrade_type]', 'neq', 'scale');
             $caller->disabledIf($name . '[modgrade_point]', $name . '[modgrade_type]', 'neq', 'point');
             $caller->disabledIf($name . '[modgrade_rescalegrades]', $name . '[modgrade_type]', 'neq', 'point');
             // Set validation rules for the sub-elements belonging to this element.
             // A handy note: the parent scope of a closure is the function in which the closure was declared.
             // Because of this using $this is safe despite the closures being called statically.
             // A nasty magic hack!
             $checkgradetypechange = function ($val) {
                 // Nothing is affected by changes to the grade type if there are no grades yet.
                 if (!$this->hasgrades) {
                     return true;
                 }
                 // Check if we are changing the grade type when grades are present.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] !== $this->currentgradetype) {
                     return false;
                 }
                 return true;
             };
             $checkscalechange = function ($val) {
                 // Nothing is affected by changes to the scale if there are no grades yet.
                 if (!$this->hasgrades) {
                     return true;
                 }
                 // Check if we are changing the scale type when grades are present.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'scale') {
                     if (isset($val['modgrade_scale']) && $val['modgrade_scale'] !== $this->currentscaleid) {
                         return false;
                     }
                 }
                 return true;
             };
             $checkmaxgradechange = function ($val) {
                 // Nothing is affected by changes to the max grade if there are no grades yet.
                 if (!$this->hasgrades) {
                     return true;
                 }
                 // If we are not using ratings we can change the max grade.
                 if (!$this->useratings) {
                     return true;
                 }
                 // Check if we are changing the max grade if we are using ratings and there is a grade.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
                     if (isset($val['modgrade_point']) && grade_floats_different($this->currentgrade, $val['modgrade_point'])) {
                         return false;
                     }
                 }
                 return true;
             };
             $checkmaxgrade = function ($val) {
                 // Closure to validate a max points value. See the note above about scope if this confuses you.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
                     if (!isset($val['modgrade_point'])) {
                         return false;
                     }
                     return $this->validate_point($val['modgrade_point']);
                 }
                 return true;
             };
             $checkvalidscale = function ($val) {
                 // Closure to validate a scale value. See the note above about scope if this confuses you.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'scale') {
                     if (!isset($val['modgrade_scale'])) {
                         return false;
                     }
                     return $this->validate_scale($val['modgrade_scale']);
                 }
                 return true;
             };
             $checkrescale = function ($val) {
                 // Nothing is affected by changes to grademax if there are no grades yet.
                 if (!$this->isupdate || !$this->hasgrades || !$this->canrescale) {
                     return true;
                 }
                 // Closure to validate a scale value. See the note above about scope if this confuses you.
                 if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
                     // Work out if the value was actually changed in the form.
                     if (grade_floats_different($this->currentgrade, $val['modgrade_point'])) {
                         if (empty($val['modgrade_rescalegrades'])) {
                             // This was an "edit", the grademax was changed and the process existing setting was not set.
                             return false;
                         }
                     }
                 }
                 return true;
             };
             $cantchangegradetype = get_string('modgradecantchangegradetype', 'grades');
             $cantchangemaxgrade = get_string('modgradecantchangeratingmaxgrade', 'grades');
             $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax'));
             $invalidscale = get_string('modgradeerrorbadscale', 'grades');
             $cantchangescale = get_string('modgradecantchangescale', 'grades');
             $mustchooserescale = get_string('mustchooserescaleyesorno', 'grades');
             // When creating the rules the sixth arg is $force, we set it to true because otherwise the form
             // will attempt to validate the existence of the element, we don't want this because the element
             // is being created right now and doesn't actually exist as a registered element yet.
             $caller->addRule($name, $cantchangegradetype, 'callback', $checkgradetypechange, 'server', false, true);
             $caller->addRule($name, $cantchangemaxgrade, 'callback', $checkmaxgradechange, 'server', false, true);
             $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true);
             $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true);
             $caller->addRule($name, $cantchangescale, 'callback', $checkscalechange, 'server', false, true);
             $caller->addRule($name, $mustchooserescale, 'callback', $checkrescale, 'server', false, true);
             break;
         case 'updateValue':
             // As this is a group element with no value of its own we are only interested in situations where the
             // default value or a constant value are being provided to the actual element.
             // In this case we expect an int that is going to translate to a scale if negative, or to max points
             // if positive.
             // Set the maximum points field to disabled if the rescale option has not been chosen and there are grades.
             $caller->disabledIf($this->getName() . '[modgrade_point]', $this->getName() . '[modgrade_rescalegrades]', 'eq', '');
             // A constant value should be given as an int.
             // The default value should be an int and should really be $CFG->gradepointdefault.
             $value = $this->_findValue($caller->_constantValues);
             if (null === $value) {
                 if ($caller->isSubmitted()) {
                     break;
                 }
                 $value = $this->_findValue($caller->_defaultValues);
             }
             if (!is_null($value) && !is_scalar($value)) {
                 // Something unexpected (likely an array of subelement values) has been given - this will be dealt
                 // with somewhere else - where exactly... likely the subelements.
                 debugging('An invalid value (type ' . gettype($value) . ') has arrived at ' . __METHOD__, DEBUG_DEVELOPER);
                 break;
             }
             // Set element state for existing data.
             // This is really a pretty hacky thing to do, when data is being set the group element is called
             // with the data first and the subelements called afterwards.
             // This means that the subelements data (inc const and default values) can be overridden by form code.
             // So - when we call this code really we can't be sure that will be the end value for the element.
             if (!empty($this->_elements)) {
                 if (!empty($value)) {
                     if ($value < 0) {
                         $this->gradetypeformelement->setValue('scale');
                         $this->scaleformelement->setValue($value * -1);
                     } else {
                         if ($value > 0) {
                             $this->gradetypeformelement->setValue('point');
                             $this->maxgradeformelement->setValue($value);
                         }
                     }
                 } else {
                     $this->gradetypeformelement->setValue('none');
                     $this->maxgradeformelement->setValue('');
                 }
             }
             break;
     }
     // Always let the parent do its thing!
     return parent::onQuickFormEvent($event, $arg, $caller);
 }
 /**
  * Internal function that does the final grade calculation
  *
  * @param int $userid The user ID
  * @param array $params An array of grade items of the form {'gi'.$itemid]} => $finalgrade
  * @param array $useditems An array of grade item IDs that this grade item depends on plus its own ID
  * @param grade_grade $oldgrade A grade_grade instance containing the old values from the database
  * @return bool False if an error occurred
  */
 public function use_formula($userid, $params, $useditems, $oldgrade)
 {
     if (empty($userid)) {
         return true;
     }
     // add missing final grade values
     // not graded (null) is counted as 0 - the spreadsheet way
     $allinputsnull = true;
     foreach ($useditems as $gi) {
         if (!array_key_exists('gi' . $gi, $params) || is_null($params['gi' . $gi])) {
             $params['gi' . $gi] = 0;
         } else {
             $params['gi' . $gi] = (double) $params['gi' . $gi];
             if ($gi != $this->id) {
                 $allinputsnull = false;
             }
         }
     }
     // can not use own final grade during calculation
     unset($params['gi' . $this->id]);
     // insert final grade - will be needed later anyway
     if ($oldgrade) {
         $oldfinalgrade = $oldgrade->finalgrade;
         $grade = new grade_grade($oldgrade, false);
         // fetching from db is not needed
         $grade->grade_item =& $this;
     } else {
         $grade = new grade_grade(array('itemid' => $this->id, 'userid' => $userid), false);
         $grade->grade_item =& $this;
         $grade->insert('system');
         $oldfinalgrade = null;
     }
     // no need to recalculate locked or overridden grades
     if ($grade->is_locked() or $grade->is_overridden()) {
         return true;
     }
     if ($allinputsnull) {
         $grade->finalgrade = null;
         $result = true;
     } else {
         // do the calculation
         $this->formula->set_params($params);
         $result = $this->formula->evaluate();
         if ($result === false) {
             $grade->finalgrade = null;
         } else {
             // normalize
             $grade->finalgrade = $this->bounded_grade($result);
         }
     }
     // update in db if changed
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
         $grade->timemodified = time();
         $grade->update('compute');
     }
     if ($result !== false) {
         //lock grade if needed
     }
     if ($result === false) {
         return false;
     } else {
         return true;
     }
 }
Example #12
0
 function validation($data, $files)
 {
     global $COURSE;
     $gradeitem = false;
     if ($data['id']) {
         $gradecategory = grade_category::fetch(array('id' => $data['id']));
         $gradeitem = $gradecategory->load_grade_item();
     }
     $errors = parent::validation($data, $files);
     if (array_key_exists('grade_item_gradetype', $data) and $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) {
         if (empty($data['grade_item_scaleid'])) {
             $errors['grade_item_scaleid'] = get_string('missingscale', 'grades');
         }
     }
     if (array_key_exists('grade_item_grademin', $data) and array_key_exists('grade_item_grademax', $data)) {
         if (($data['grade_item_grademax'] != 0 or $data['grade_item_grademin'] != 0) and ($data['grade_item_grademax'] == $data['grade_item_grademin'] or $data['grade_item_grademax'] < $data['grade_item_grademin'])) {
             $errors['grade_item_grademin'] = get_string('incorrectminmax', 'grades');
             $errors['grade_item_grademax'] = get_string('incorrectminmax', 'grades');
         }
     }
     if ($data['id'] && $gradeitem->has_overridden_grades()) {
         if ($gradeitem->gradetype == GRADE_TYPE_VALUE) {
             if (grade_floats_different($data['grade_item_grademin'], $gradeitem->grademin) || grade_floats_different($data['grade_item_grademax'], $gradeitem->grademax)) {
                 if (empty($data['grade_item_rescalegrades'])) {
                     $errors['grade_item_rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades');
                 }
             }
         }
     }
     return $errors;
 }
Example #13
0
 function validation($data, $files)
 {
     global $COURSE;
     $grade_item = false;
     if ($data['id']) {
         $grade_item = new grade_item(array('id' => $data['id'], 'courseid' => $data['courseid']));
     }
     $errors = parent::validation($data, $files);
     if (array_key_exists('idnumber', $data)) {
         if ($grade_item) {
             if ($grade_item->itemtype == 'mod') {
                 $cm = get_coursemodule_from_instance($grade_item->itemmodule, $grade_item->iteminstance, $grade_item->courseid);
             } else {
                 $cm = null;
             }
         } else {
             $grade_item = null;
             $cm = null;
         }
         if (!grade_verify_idnumber($data['idnumber'], $COURSE->id, $grade_item, $cm)) {
             $errors['idnumber'] = get_string('idnumbertaken');
         }
     }
     if (array_key_exists('gradetype', $data) and $data['gradetype'] == GRADE_TYPE_SCALE) {
         if (empty($data['scaleid'])) {
             $errors['scaleid'] = get_string('missingscale', 'grades');
         }
     }
     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');
         }
     }
     // We do not want the user to be able to change the grade type or scale for this item if grades exist.
     if ($grade_item && $grade_item->has_grades()) {
         // Check that grade type is set - should never not be set unless form has been modified.
         if (!isset($data['gradetype'])) {
             $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades');
         } else {
             if ($data['gradetype'] !== $grade_item->gradetype) {
                 // Check if we are changing the grade type.
                 $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades');
             } else {
                 if ($data['gradetype'] == GRADE_TYPE_SCALE) {
                     // Check if we are changing the scale - can't do this when grades exist.
                     if (isset($data['scaleid']) && $data['scaleid'] !== $grade_item->scaleid) {
                         $errors['scaleid'] = get_string('modgradecantchangescale', 'grades');
                     }
                 }
             }
         }
     }
     if ($grade_item) {
         if ($grade_item->gradetype == GRADE_TYPE_VALUE) {
             if (grade_floats_different($data['grademin'], $grade_item->grademin) || grade_floats_different($data['grademax'], $grade_item->grademax)) {
                 if ($grade_item->has_grades() && empty($data['rescalegrades'])) {
                     $errors['rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades');
                 }
             }
         }
     }
     return $errors;
 }
 /**
  * Given an array of all assessments done by a single reviewer, calculates the final grading grade
  *
  * This calculates the simple mean of the passed grading grades. If, however, the grading grade
  * was overridden by a teacher, the gradinggradeover value is returned and the rest of grades are ignored.
  *
  * @param array $assessments of stdclass(->reviewerid ->gradinggrade ->gradinggradeover ->aggregationid ->aggregatedgrade)
  * @param null|int $timegraded explicit timestamp of the aggregation, defaults to the current time
  * @return void
  */
 protected function aggregate_grading_grades_process(array $assessments, $timegraded = null)
 {
     global $DB;
     $reviewerid = null;
     // the id of the reviewer being processed
     $current = null;
     // the gradinggrade currently saved in database
     $finalgrade = null;
     // the new grade to be calculated
     $agid = null;
     // aggregation id
     $sumgrades = 0;
     $count = 0;
     if (is_null($timegraded)) {
         $timegraded = time();
     }
     foreach ($assessments as $assessment) {
         if (is_null($reviewerid)) {
             // the id is the same in all records, fetch it during the first loop cycle
             $reviewerid = $assessment->reviewerid;
         }
         if (is_null($agid)) {
             // the id is the same in all records, fetch it during the first loop cycle
             $agid = $assessment->aggregationid;
         }
         if (is_null($current)) {
             // the currently saved grade is the same in all records, fetch it during the first loop cycle
             $current = $assessment->aggregatedgrade;
         }
         if (!is_null($assessment->gradinggradeover)) {
             // the grading grade for this assessment is overridden by a teacher
             $sumgrades += $assessment->gradinggradeover;
             $count++;
         } else {
             if (!is_null($assessment->gradinggrade)) {
                 $sumgrades += $assessment->gradinggrade;
                 $count++;
             }
         }
     }
     if ($count > 0) {
         $finalgrade = grade_floatval($sumgrades / $count);
     }
     // Event information.
     $params = array('context' => $this->context, 'courseid' => $this->course->id, 'relateduserid' => $reviewerid);
     // check if the new final grade differs from the one stored in the database
     if (grade_floats_different($finalgrade, $current)) {
         $params['other'] = array('currentgrade' => $current, 'finalgrade' => $finalgrade);
         // we need to save new calculation into the database
         if (is_null($agid)) {
             // no aggregation record yet
             $record = new stdclass();
             $record->workshopid = $this->id;
             $record->userid = $reviewerid;
             $record->gradinggrade = $finalgrade;
             $record->timegraded = $timegraded;
             $record->id = $DB->insert_record('workshop_aggregations', $record);
             $params['objectid'] = $record->id;
             $event = \mod_workshop\event\assessment_evaluated::create($params);
             $event->trigger();
         } else {
             $record = new stdclass();
             $record->id = $agid;
             $record->gradinggrade = $finalgrade;
             $record->timegraded = $timegraded;
             $DB->update_record('workshop_aggregations', $record);
             $params['objectid'] = $agid;
             $event = \mod_workshop\event\assessment_reevaluated::create($params);
             $event->trigger();
         }
     }
 }
Example #15
0
             $data->finalgrade = $old_grade_grade->finalgrade;
         }
     }
 }
 // the overriding of feedback is tricky - we have to care about external items only
 if (!array_key_exists('feedback', $data) or $data->feedback == $data->oldfeedback) {
     $data->feedback = $old_grade_grade->feedback;
     $data->feedbackformat = $old_grade_grade->feedbackformat;
 }
 // update final grade or feedback
 $grade_item->update_final_grade($data->userid, $data->finalgrade, 'editgrade', $data->feedback, $data->feedbackformat);
 $grade_grade = new grade_grade(array('userid' => $data->userid, 'itemid' => $grade_item->id), true);
 $grade_grade->grade_item =& $grade_item;
 // no db fetching
 if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:edit', $context)) {
     if (!grade_floats_different($data->finalgrade, $old_grade_grade->finalgrade) and $data->feedback === $old_grade_grade->feedback) {
         // change overridden flag only if grade or feedback not changed
         if (!isset($data->overridden)) {
             $data->overridden = 0;
             // checkbox
         }
         $grade_grade->set_overridden($data->overridden);
     }
 }
 if (has_capability('moodle/grade:manage', $context) or has_capability('moodle/grade:hide', $context)) {
     $hidden = empty($data->hidden) ? 0 : $data->hidden;
     $hiddenuntil = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil;
     if ($grade_item->is_hidden()) {
         if ($old_grade_grade->hidden == 1 and $hiddenuntil == 0) {
             //nothing to do - grade was originally hidden, we want to keep it that way
         } else {
Example #16
0
 /**
  * Internal function for category grades summing
  *
  * @param grade_grade $grade The grade item
  * @param float $oldfinalgrade Old Final grade
  * @param array $items Grade items
  * @param array $grade_values Grade values
  * @param array $excluded Excluded
  */
 private function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded)
 {
     if (empty($items)) {
         return null;
     }
     // ungraded and excluded items are not used in aggregation
     foreach ($grade_values as $itemid => $v) {
         if (is_null($v)) {
             unset($grade_values[$itemid]);
         } else {
             if (in_array($itemid, $excluded)) {
                 unset($grade_values[$itemid]);
             }
         }
     }
     // use 0 if grade missing, droplow used and aggregating all items
     if (!$this->aggregateonlygraded and !empty($this->droplow)) {
         foreach ($items as $itemid => $value) {
             if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
                 $grade_values[$itemid] = 0;
             }
         }
     }
     $this->apply_limit_rules($grade_values, $items);
     $sum = array_sum($grade_values);
     $grade->finalgrade = $this->grade_item->bounded_grade($sum);
     // update in db if changed
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
         $grade->update('aggregation');
     }
     return;
 }
Example #17
0
    /**
     * Save quick grades.
     *
     * @return string The result of the save operation
     */
    protected function process_save_quick_grades() {
        global $USER, $DB, $CFG;

        // Need grade permission.
        require_capability('mod/assign:grade', $this->context);
        require_sesskey();

        // Make sure advanced grading is disabled.
        $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
        $controller = $gradingmanager->get_active_controller();
        if (!empty($controller)) {
            return get_string('errorquickgradingvsadvancedgrading', 'assign');
        }

        $users = array();
        // First check all the last modified values.
        $currentgroup = groups_get_activity_group($this->get_course_module(), true);
        $participants = $this->list_participants($currentgroup, true);

        // Gets a list of possible users and look for values based upon that.
        foreach ($participants as $userid => $unused) {
            $modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
            $attemptnumber = optional_param('gradeattempt_' . $userid, -1, PARAM_INT);
            // Gather the userid, updated grade and last modified value.
            $record = new stdClass();
            $record->userid = $userid;
            if ($modified >= 0) {
                $record->grade = unformat_float(optional_param('quickgrade_' . $record->userid, -1, PARAM_TEXT));
                $record->workflowstate = optional_param('quickgrade_' . $record->userid.'_workflowstate', false, PARAM_TEXT);
                $record->allocatedmarker = optional_param('quickgrade_' . $record->userid.'_allocatedmarker', false, PARAM_INT);
            } else {
                // This user was not in the grading table.
                continue;
            }
            $record->attemptnumber = $attemptnumber;
            $record->lastmodified = $modified;
            $record->gradinginfo = grade_get_grades($this->get_course()->id,
                                                    'mod',
                                                    'assign',
                                                    $this->get_instance()->id,
                                                    array($userid));
            $users[$userid] = $record;
        }

        if (empty($users)) {
            return get_string('nousersselected', 'assign');
        }

        list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
        $params['assignid1'] = $this->get_instance()->id;
        $params['assignid2'] = $this->get_instance()->id;

        // Check them all for currency.
        $grademaxattempt = 'SELECT s.userid, s.attemptnumber AS maxattempt
                              FROM {assign_submission} s
                             WHERE s.assignment = :assignid1 AND s.latest = 1';

        $sql = 'SELECT u.id AS userid, g.grade AS grade, g.timemodified AS lastmodified,
                       uf.workflowstate, uf.allocatedmarker, gmx.maxattempt AS attemptnumber
                  FROM {user} u
             LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
             LEFT JOIN {assign_grades} g ON
                       u.id = g.userid AND
                       g.assignment = :assignid2 AND
                       g.attemptnumber = gmx.maxattempt
             LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid
                 WHERE u.id ' . $userids;
        $currentgrades = $DB->get_recordset_sql($sql, $params);

        $modifiedusers = array();
        foreach ($currentgrades as $current) {
            $modified = $users[(int)$current->userid];
            $grade = $this->get_user_grade($modified->userid, false);
            // Check to see if the grade column was even visible.
            $gradecolpresent = optional_param('quickgrade_' . $modified->userid, false, PARAM_INT) !== false;

            // Check to see if the outcomes were modified.
            if ($CFG->enableoutcomes) {
                foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
                    $oldoutcome = $outcome->grades[$modified->userid]->grade;
                    $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
                    $newoutcome = optional_param($paramname, -1, PARAM_FLOAT);
                    // Check to see if the outcome column was even visible.
                    $outcomecolpresent = optional_param($paramname, false, PARAM_FLOAT) !== false;
                    if ($outcomecolpresent && ($oldoutcome != $newoutcome)) {
                        // Can't check modified time for outcomes because it is not reported.
                        $modifiedusers[$modified->userid] = $modified;
                        continue;
                    }
                }
            }

            // Let plugins participate.
            foreach ($this->feedbackplugins as $plugin) {
                if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
                    // The plugins must handle is_quickgrading_modified correctly - ie
                    // handle hidden columns.
                    if ($plugin->is_quickgrading_modified($modified->userid, $grade)) {
                        if ((int)$current->lastmodified > (int)$modified->lastmodified) {
                            return get_string('errorrecordmodified', 'assign');
                        } else {
                            $modifiedusers[$modified->userid] = $modified;
                            continue;
                        }
                    }
                }
            }

            if (($current->grade < 0 || $current->grade === null) &&
                ($modified->grade < 0 || $modified->grade === null)) {
                // Different ways to indicate no grade.
                $modified->grade = $current->grade; // Keep existing grade.
            }
            // Treat 0 and null as different values.
            if ($current->grade !== null) {
                $current->grade = floatval($current->grade);
            }
            $gradechanged = $gradecolpresent && grade_floats_different($current->grade, $modified->grade);
            $markingallocationchanged = $this->get_instance()->markingworkflow &&
                                        $this->get_instance()->markingallocation &&
                                            ($modified->allocatedmarker !== false) &&
                                            ($current->allocatedmarker != $modified->allocatedmarker);
            $workflowstatechanged = $this->get_instance()->markingworkflow &&
                                            ($modified->workflowstate !== false) &&
                                            ($current->workflowstate != $modified->workflowstate);
            if ($gradechanged || $markingallocationchanged || $workflowstatechanged) {
                // Grade changed.
                if ($this->grading_disabled($modified->userid)) {
                    continue;
                }
                $badmodified = (int)$current->lastmodified > (int)$modified->lastmodified;
                $badattempt = (int)$current->attemptnumber != (int)$modified->attemptnumber;
                if ($badmodified || $badattempt) {
                    // Error - record has been modified since viewing the page.
                    return get_string('errorrecordmodified', 'assign');
                } else {
                    $modifiedusers[$modified->userid] = $modified;
                }
            }

        }
        $currentgrades->close();

        $adminconfig = $this->get_admin_config();
        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;

        // Ok - ready to process the updates.
        foreach ($modifiedusers as $userid => $modified) {
            $grade = $this->get_user_grade($userid, true);
            $flags = $this->get_user_flags($userid, true);
            $grade->grade= grade_floatval(unformat_float($modified->grade));
            $grade->grader= $USER->id;
            $gradecolpresent = optional_param('quickgrade_' . $userid, false, PARAM_INT) !== false;

            // Save plugins data.
            foreach ($this->feedbackplugins as $plugin) {
                if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
                    $plugin->save_quickgrading_changes($userid, $grade);
                    if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
                        // This is the feedback plugin chose to push comments to the gradebook.
                        $grade->feedbacktext = $plugin->text_for_gradebook($grade);
                        $grade->feedbackformat = $plugin->format_for_gradebook($grade);
                    }
                }
            }

            // These will be set to false if they are not present in the quickgrading
            // form (e.g. column hidden).
            $workflowstatemodified = ($modified->workflowstate !== false) &&
                                        ($flags->workflowstate != $modified->workflowstate);

            $allocatedmarkermodified = ($modified->allocatedmarker !== false) &&
                                        ($flags->allocatedmarker != $modified->allocatedmarker);

            if ($workflowstatemodified) {
                $flags->workflowstate = $modified->workflowstate;
            }
            if ($allocatedmarkermodified) {
                $flags->allocatedmarker = $modified->allocatedmarker;
            }
            if ($workflowstatemodified || $allocatedmarkermodified) {
                if ($this->update_user_flags($flags) && $workflowstatemodified) {
                    $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
                    \mod_assign\event\workflow_state_updated::create_from_user($this, $user, $flags->workflowstate)->trigger();
                }
            }
            $this->update_grade($grade);

            // Allow teachers to skip sending notifications.
            if (optional_param('sendstudentnotifications', true, PARAM_BOOL)) {
                $this->notify_grade_modified($grade, true);
            }

            // Save outcomes.
            if ($CFG->enableoutcomes) {
                $data = array();
                foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
                    $oldoutcome = $outcome->grades[$modified->userid]->grade;
                    $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
                    // This will be false if the input was not in the quickgrading
                    // form (e.g. column hidden).
                    $newoutcome = optional_param($paramname, false, PARAM_INT);
                    if ($newoutcome !== false && ($oldoutcome != $newoutcome)) {
                        $data[$outcomeid] = $newoutcome;
                    }
                }
                if (count($data) > 0) {
                    grade_update_outcomes('mod/assign',
                                          $this->course->id,
                                          'mod',
                                          'assign',
                                          $this->get_instance()->id,
                                          $userid,
                                          $data);
                }
            }
        }

        return get_string('quickgradingchangessaved', 'assign');
    }
Example #18
0
 /**
  * Calculates the aggregated grade given by the reviewer
  *
  * @param array $grades Grade records as returned by {@link get_current_assessment_data}
  * @return float|null   Raw grade (0.00000 to 100.00000) for submission as suggested by the peer
  */
 protected function calculate_peer_grade(array $grades) {
     if (empty($grades)) {
         return null;
     }
     $sumerrors  = 0;    // sum of the weighted errors (ie the negative responses)
     foreach ($grades as $grade) {
         if (grade_floats_different($grade->grade, 1.00000)) {
             // negative reviewer's response
             $sumerrors += $this->dimensions[$grade->dimensionid]->weight;
         }
     }
     return $this->errors_to_grade($sumerrors);
 }
Example #19
0
 /**
  * Set the flags on the grade_grade items to indicate how individual grades are used
  * in the aggregation.
  *
  * WARNING: This function is called a lot during gradebook recalculation, be very performance considerate.
  *
  * @param int $userid The user we have aggregated the grades for.
  * @param array $usedweights An array with keys for each of the grade_item columns included in the aggregation. The value are the relative weight.
  * @param array $novalue An array with keys for each of the grade_item columns skipped because
  *                       they had no value in the aggregation.
  * @param array $dropped An array with keys for each of the grade_item columns dropped
  *                       because of any drop lowest/highest settings in the aggregation.
  * @param array $extracredit An array with keys for each of the grade_item columns
  *                       considered extra credit by the aggregation.
  */
 private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit)
 {
     global $DB;
     // We want to know all current user grades so we can decide whether they need to be updated or they already contain the
     // expected value.
     $sql = "SELECT gi.id, gg.aggregationstatus, gg.aggregationweight FROM {grade_grades} gg\n                  JOIN {grade_items} gi ON (gg.itemid = gi.id)\n                 WHERE gg.userid = :userid";
     $params = array('categoryid' => $this->id, 'userid' => $userid);
     // These are all grade_item ids which grade_grades will NOT end up being 'unknown' (because they are not unknown or
     // because we will update them to something different that 'unknown').
     $giids = array_keys($usedweights + $novalue + $dropped + $extracredit);
     if ($giids) {
         // We include grade items that might not be in categoryid.
         list($itemsql, $itemlist) = $DB->get_in_or_equal($giids, SQL_PARAMS_NAMED, 'gg');
         $sql .= ' AND (gi.categoryid = :categoryid OR gi.id ' . $itemsql . ')';
         $params = $params + $itemlist;
     } else {
         $sql .= ' AND gi.categoryid = :categoryid';
     }
     $currentgrades = $DB->get_recordset_sql($sql, $params);
     // We will store here the grade_item ids that need to be updated on db.
     $toupdate = array();
     if ($currentgrades->valid()) {
         // Iterate through the user grades to see if we really need to update any of them.
         foreach ($currentgrades as $currentgrade) {
             // Unset $usedweights that we do not need to update.
             if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) && $currentgrade->aggregationstatus === 'used') {
                 // We discard the ones that already have the contribution specified in $usedweights and are marked as 'used'.
                 if (grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) {
                     unset($usedweights[$currentgrade->id]);
                 }
                 // Used weights can be present in multiple set_usedinaggregation arguments.
                 if (!isset($novalue[$currentgrade->id]) && !isset($dropped[$currentgrade->id]) && !isset($extracredit[$currentgrade->id])) {
                     continue;
                 }
             }
             // No value grades.
             if (!empty($novalue) && isset($novalue[$currentgrade->id])) {
                 if ($currentgrade->aggregationstatus !== 'novalue' || grade_floats_different($currentgrade->aggregationweight, 0)) {
                     $toupdate['novalue'][] = $currentgrade->id;
                 }
                 continue;
             }
             // Dropped grades.
             if (!empty($dropped) && isset($dropped[$currentgrade->id])) {
                 if ($currentgrade->aggregationstatus !== 'dropped' || grade_floats_different($currentgrade->aggregationweight, 0)) {
                     $toupdate['dropped'][] = $currentgrade->id;
                 }
                 continue;
             }
             // Extra credit grades.
             if (!empty($extracredit) && isset($extracredit[$currentgrade->id])) {
                 // If this grade item is already marked as 'extra' and it already has the provided $usedweights value would be
                 // silly to update to 'used' to later update to 'extra'.
                 if (!empty($usedweights) && isset($usedweights[$currentgrade->id]) && grade_floats_equal($currentgrade->aggregationweight, $usedweights[$currentgrade->id])) {
                     unset($usedweights[$currentgrade->id]);
                 }
                 // Update the item to extra if it is not already marked as extra in the database or if the item's
                 // aggregationweight will be updated when going through $usedweights items.
                 if ($currentgrade->aggregationstatus !== 'extra' || !empty($usedweights) && isset($usedweights[$currentgrade->id])) {
                     $toupdate['extracredit'][] = $currentgrade->id;
                 }
                 continue;
             }
             // If is not in any of the above groups it should be set to 'unknown', checking that the item is not already
             // unknown, if it is we don't need to update it.
             if ($currentgrade->aggregationstatus !== 'unknown' || grade_floats_different($currentgrade->aggregationweight, 0)) {
                 $toupdate['unknown'][] = $currentgrade->id;
             }
         }
         $currentgrades->close();
     }
     // Update items to 'unknown' status.
     if (!empty($toupdate['unknown'])) {
         list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['unknown'], SQL_PARAMS_NAMED, 'g');
         $itemlist['userid'] = $userid;
         $sql = "UPDATE {grade_grades}\n                       SET aggregationstatus = 'unknown',\n                           aggregationweight = 0\n                     WHERE itemid {$itemsql} AND userid = :userid";
         $DB->execute($sql, $itemlist);
     }
     // Update items to 'used' status and setting the proper weight.
     if (!empty($usedweights)) {
         // The usedweights items are updated individually to record the weights.
         foreach ($usedweights as $gradeitemid => $contribution) {
             $sql = "UPDATE {grade_grades}\n                           SET aggregationstatus = 'used',\n                               aggregationweight = :contribution\n                         WHERE itemid = :itemid AND userid = :userid";
             $params = array('contribution' => $contribution, 'itemid' => $gradeitemid, 'userid' => $userid);
             $DB->execute($sql, $params);
         }
     }
     // Update items to 'novalue' status.
     if (!empty($toupdate['novalue'])) {
         list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['novalue'], SQL_PARAMS_NAMED, 'g');
         $itemlist['userid'] = $userid;
         $sql = "UPDATE {grade_grades}\n                       SET aggregationstatus = 'novalue',\n                           aggregationweight = 0\n                     WHERE itemid {$itemsql} AND userid = :userid";
         $DB->execute($sql, $itemlist);
     }
     // Update items to 'dropped' status.
     if (!empty($toupdate['dropped'])) {
         list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['dropped'], SQL_PARAMS_NAMED, 'g');
         $itemlist['userid'] = $userid;
         $sql = "UPDATE {grade_grades}\n                       SET aggregationstatus = 'dropped',\n                           aggregationweight = 0\n                     WHERE itemid {$itemsql} AND userid = :userid";
         $DB->execute($sql, $itemlist);
     }
     // Update items to 'extracredit' status.
     if (!empty($toupdate['extracredit'])) {
         list($itemsql, $itemlist) = $DB->get_in_or_equal($toupdate['extracredit'], SQL_PARAMS_NAMED, 'g');
         $itemlist['userid'] = $userid;
         $DB->set_field_select('grade_grades', 'aggregationstatus', 'extra', "itemid {$itemsql} AND userid = :userid", $itemlist);
     }
 }