Пример #1
0
 /**
  * This function is called after the table has been built and the aggregationhints
  * have been collected. We need this info to walk up the list of parents of each
  * grade_item.
  *
  * @param $element - An array containing the table data for the current row.
  */
 public function fill_contributions_column($element)
 {
     // Recursively iterate through all child elements.
     if (isset($element['children'])) {
         foreach ($element['children'] as $key => $child) {
             $this->fill_contributions_column($element['children'][$key]);
         }
     } else {
         if ($element['type'] == 'item') {
             // This is a grade item (We don't do this for categories or we would double count).
             $grade_object = $element['object'];
             $itemid = $grade_object->id;
             // Ignore anything with no hint - e.g. a hidden row.
             if (isset($this->aggregationhints[$itemid])) {
                 // Normalise the gradeval.
                 $gradecat = $grade_object->load_parent_category();
                 if ($gradecat->aggregation == GRADE_AGGREGATE_SUM) {
                     // Natural aggregation/Sum of grades does not consider the mingrade, cannot traditionnally normalise it.
                     $graderange = $this->aggregationhints[$itemid]['grademax'];
                     if ($graderange != 0) {
                         $gradeval = $this->aggregationhints[$itemid]['grade'] / $graderange;
                     } else {
                         $gradeval = 0;
                     }
                 } else {
                     $gradeval = grade_grade::standardise_score($this->aggregationhints[$itemid]['grade'], $this->aggregationhints[$itemid]['grademin'], $this->aggregationhints[$itemid]['grademax'], 0, 1);
                 }
                 // Multiply the normalised value by the weight
                 // of all the categories higher in the tree.
                 $parent = null;
                 do {
                     if (!is_null($this->aggregationhints[$itemid]['weight'])) {
                         $gradeval *= $this->aggregationhints[$itemid]['weight'];
                     } else {
                         if (empty($parent)) {
                             // If we are in the first loop, and the weight is null, then we cannot calculate the contribution.
                             $gradeval = null;
                             break;
                         }
                     }
                     // The second part of this if is to prevent infinite loops
                     // in case of crazy data.
                     if (isset($this->aggregationhints[$itemid]['parent']) && $this->aggregationhints[$itemid]['parent'] != $itemid) {
                         $parent = $this->aggregationhints[$itemid]['parent'];
                         $itemid = $parent;
                     } else {
                         // We are at the top of the tree.
                         $parent = false;
                     }
                 } while ($parent);
                 // Finally multiply by the course grademax.
                 if (!is_null($gradeval)) {
                     // Convert to percent.
                     $gradeval *= 100;
                 }
                 // Now we need to loop through the "built" table data and update the
                 // contributions column for the current row.
                 $header_row = "row_{$grade_object->id}_{$this->user->id}";
                 foreach ($this->tabledata as $key => $row) {
                     if (isset($row['itemname']) && $row['itemname']['id'] == $header_row) {
                         // Found it - update the column.
                         $content = '-';
                         if (!is_null($gradeval)) {
                             $decimals = $grade_object->get_decimals();
                             $content = format_float($gradeval, $decimals, true) . ' %';
                         }
                         $this->tabledata[$key]['contributiontocoursetotal']['content'] = $content;
                         break;
                     }
                 }
             }
         }
     }
 }
Пример #2
0
/**
 * Returns string representation of grade value
 * @param float $value grade value
 * @param object $grade_item - by reference to prevent scale reloading
 * @param bool $localized use localised decimal separator
 * @param int $display type of display - raw, letter, percentage
 * @param int $decimalplaces number of decimal places when displaying float values
 * @return string
 */
function grade_format_gradevalue($value, &$grade_item, $localized = true, $displaytype = null, $decimals = null)
{
    if ($grade_item->gradetype == GRADE_TYPE_NONE or $grade_item->gradetype == GRADE_TYPE_TEXT) {
        return '';
    }
    // no grade yet?
    if (is_null($value)) {
        return '-';
    }
    if ($grade_item->gradetype != GRADE_TYPE_VALUE and $grade_item->gradetype != GRADE_TYPE_SCALE) {
        //unknown type??
        return '';
    }
    if (is_null($displaytype)) {
        $displaytype = $grade_item->get_displaytype();
    }
    if (is_null($decimals)) {
        $decimals = $grade_item->get_decimals();
    }
    switch ($displaytype) {
        case GRADE_DISPLAY_TYPE_REAL:
            if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
                $scale = $grade_item->load_scale();
                $value = (int) bounded_number($grade_item->grademin, $value, $grade_item->grademax);
                return format_string($scale->scale_items[$value - 1]);
            } else {
                return format_float($value, $decimals, $localized);
            }
        case GRADE_DISPLAY_TYPE_PERCENTAGE:
            $min = $grade_item->grademin;
            $max = $grade_item->grademax;
            if ($min == $max) {
                return '';
            }
            $value = bounded_number($min, $value, $max);
            $percentage = ($value - $min) * 100 / ($max - $min);
            return format_float($percentage, $decimals, $localized) . ' %';
        case GRADE_DISPLAY_TYPE_LETTER:
            $context = get_context_instance(CONTEXT_COURSE, $grade_item->courseid);
            if (!($letters = grade_get_letters($context))) {
                return '';
                // no letters??
            }
            $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
            $value = bounded_number(0, $value, 100);
            // just in case
            foreach ($letters as $boundary => $letter) {
                if ($value >= $boundary) {
                    return format_string($letter);
                }
            }
            return '-';
            // no match? maybe '' would be more correct
        // no match? maybe '' would be more correct
        default:
            return '';
    }
}
Пример #3
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
  */
 private function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded)
 {
     global $CFG;
     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]);
     // sum is a special aggregation types - it adjusts the min max, does not use relative values
     if ($this->aggregation == GRADE_AGGREGATE_SUM) {
         $this->sum_grades($grade, $oldfinalgrade, $items, $grade_values, $excluded);
         return;
     }
     // 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);
     }
     // 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);
     // 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
     $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);
     $grade->finalgrade = $this->grade_item->bounded_grade($finalgrade);
     // update in db if changed
     if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
         $grade->update('aggregation');
     }
     return;
 }
Пример #4
0
 /**
  * Given a float grade value or integer grade scale, applies a number of adjustment based on
  * grade_item variables and returns the result.
  * @param float $rawgrade The raw grade value.
  * @param float $rawmin original rawmin
  * @param float $rawmax original rawmax
  * @return mixed
  */
 function adjust_raw_grade($rawgrade, $rawmin, $rawmax)
 {
     if (is_null($rawgrade)) {
         return null;
     }
     if ($this->gradetype == GRADE_TYPE_VALUE) {
         // Dealing with numerical grade
         if ($this->grademax < $this->grademin) {
             return null;
         }
         if ($this->grademax == $this->grademin) {
             return $this->grademax;
             // no range
         }
         // Standardise score to the new grade range
         // NOTE: this is not compatible with current assignment grading
         if ($this->itemmodule != 'assignment' and ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
             $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
         }
         // Apply other grade_item factors
         $rawgrade *= $this->multfactor;
         $rawgrade += $this->plusfactor;
         return bounded_number($this->grademin, $rawgrade, $this->grademax);
     } else {
         if ($this->gradetype == GRADE_TYPE_SCALE) {
             // Dealing with a scale value
             if (empty($this->scale)) {
                 $this->load_scale();
             }
             if ($this->grademax < 0) {
                 return null;
                 // scale not present - no grade
             }
             if ($this->grademax == 0) {
                 return $this->grademax;
                 // only one option
             }
             // Convert scale if needed
             // NOTE: this is not compatible with current assignment grading
             if ($this->itemmodule != 'assignment' and ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
                 $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
             }
             return (int) bounded_number(0, round($rawgrade + 1.0E-5), $this->grademax);
         } else {
             if ($this->gradetype == GRADE_TYPE_TEXT or $this->gradetype == GRADE_TYPE_NONE) {
                 // no value
                 // somebody changed the grading type when grades already existed
                 return null;
             } else {
                 debugging("Unknown grade type");
                 return null;
             }
         }
     }
 }
Пример #5
0
/**
 * Returns a letter grade representation of a grade value
 * The array of grade letters used is produced by {@link grade_get_letters()} using the course context
 *
 * @param float $value The grade value
 * @param object $grade_item Grade item object
 * @return string
 */
function grade_format_gradevalue_letter($value, $grade_item)
{
    $context = get_context_instance(CONTEXT_COURSE, $grade_item->courseid);
    if (!($letters = grade_get_letters($context))) {
        return '';
        // no letters??
    }
    if (is_null($value)) {
        return '-';
    }
    $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
    $value = bounded_number(0, $value, 100);
    // just in case
    foreach ($letters as $boundary => $letter) {
        if ($value >= $boundary) {
            return format_string($letter);
        }
    }
    return '-';
    // no match? maybe '' would be more correct
}
Пример #6
0
 /**
  * Return array of grade item ids that are either hidden or indirectly depend
  * on hidden grades, excluded grades are not returned.
  * THIS IS A REALLY BIG HACK! to be replaced by conditional aggregation of hidden grades in 2.0
  *
  * @static
  * @param array $grades all course grades of one user, & used for better internal caching
  * @param array $items $grade_items array of grade items, & used for better internal caching
  * @return array
  */
 public static function get_hiding_affected(&$grade_grades, &$grade_items)
 {
     global $CFG;
     if (count($grade_grades) !== count($grade_items)) {
         print_error('invalidarraysize', 'debug', '', 'grade_grade::get_hiding_affected()!');
     }
     $dependson = array();
     $todo = array();
     $unknown = array();
     // can not find altered
     $altered = array();
     // altered grades
     $hiddenfound = false;
     foreach ($grade_grades as $itemid => $unused) {
         $grade_grade =& $grade_grades[$itemid];
         if ($grade_grade->is_excluded()) {
             //nothing to do, aggregation is ok
         } else {
             if ($grade_grade->is_hidden()) {
                 $hiddenfound = true;
                 $altered[$grade_grade->itemid] = null;
             } else {
                 if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
                     // no need to recalculate locked or overridden grades
                 } else {
                     $dependson[$grade_grade->itemid] = $grade_items[$grade_grade->itemid]->depends_on();
                     if (!empty($dependson[$grade_grade->itemid])) {
                         $todo[] = $grade_grade->itemid;
                     }
                 }
             }
         }
     }
     if (!$hiddenfound) {
         return array('unknown' => array(), 'altered' => array());
     }
     $max = count($todo);
     $hidden_precursors = null;
     for ($i = 0; $i < $max; $i++) {
         $found = false;
         foreach ($todo as $key => $do) {
             $hidden_precursors = array_intersect($dependson[$do], $unknown);
             if ($hidden_precursors) {
                 // this item depends on hidden grade indirectly
                 $unknown[$do] = $do;
                 unset($todo[$key]);
                 $found = true;
                 continue;
             } else {
                 if (!array_intersect($dependson[$do], $todo)) {
                     $hidden_precursors = array_intersect($dependson[$do], array_keys($altered));
                     if (!$hidden_precursors) {
                         // hiding does not affect this grade
                         unset($todo[$key]);
                         $found = true;
                         continue;
                     } else {
                         // depends on altered grades - we should try to recalculate if possible
                         if ($grade_items[$do]->is_calculated() or !$grade_items[$do]->is_category_item() and !$grade_items[$do]->is_course_item()) {
                             $unknown[$do] = $do;
                             unset($todo[$key]);
                             $found = true;
                             continue;
                         } else {
                             $grade_category = $grade_items[$do]->load_item_category();
                             $values = array();
                             foreach ($dependson[$do] as $itemid) {
                                 if (array_key_exists($itemid, $altered)) {
                                     //nulling an altered precursor
                                     $values[$itemid] = $altered[$itemid];
                                 } elseif (empty($values[$itemid])) {
                                     $values[$itemid] = $grade_grades[$itemid]->finalgrade;
                                 }
                             }
                             foreach ($values as $itemid => $value) {
                                 if ($grade_grades[$itemid]->is_excluded()) {
                                     unset($values[$itemid]);
                                     continue;
                                 }
                                 $values[$itemid] = grade_grade::standardise_score($value, $grade_items[$itemid]->grademin, $grade_items[$itemid]->grademax, 0, 1);
                             }
                             if ($grade_category->aggregateonlygraded) {
                                 foreach ($values as $itemid => $value) {
                                     if (is_null($value)) {
                                         unset($values[$itemid]);
                                     }
                                 }
                             } else {
                                 foreach ($values as $itemid => $value) {
                                     if (is_null($value)) {
                                         $values[$itemid] = 0;
                                     }
                                 }
                             }
                             // limit and sort
                             $grade_category->apply_limit_rules($values, $grade_items);
                             asort($values, SORT_NUMERIC);
                             // let's see we have still enough grades to do any statistics
                             if (count($values) == 0) {
                                 // not enough attempts yet
                                 $altered[$do] = null;
                                 unset($todo[$key]);
                                 $found = true;
                                 continue;
                             }
                             $agg_grade = $grade_category->aggregate_values($values, $grade_items);
                             // recalculate the rawgrade back to requested range
                             $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $grade_items[$do]->grademin, $grade_items[$do]->grademax);
                             $finalgrade = $grade_items[$do]->bounded_grade($finalgrade);
                             $altered[$do] = $finalgrade;
                             unset($todo[$key]);
                             $found = true;
                             continue;
                         }
                     }
                 }
             }
         }
         if (!$found) {
             break;
         }
     }
     return array('unknown' => $unknown, 'altered' => $altered);
 }
Пример #7
0
 /**
  * Return array of grade item ids that are either hidden or indirectly depend
  * on hidden grades, excluded grades are not returned.
  * THIS IS A REALLY BIG HACK! to be replaced by conditional aggregation of hidden grades in 2.0
  *
  * @param array $grade_grades all course grades of one user, & used for better internal caching
  * @param array $grade_items array of grade items, & used for better internal caching
  * @return array This is an array of 3 arrays:
  *      unknown => list of item ids that may be affected by hiding (with the calculated grade as the value)
  *      altered => list of item ids that are definitely affected by hiding (with the calculated grade as the value)
  *      alteredgrademax => for each item in altered or unknown, the new value of the grademax
  *      alteredgrademin => for each item in altered or unknown, the new value of the grademin
  *      alteredgradestatus => for each item with a modified status - the value of the new status
  *      alteredgradeweight => for each item with a modified weight - the value of the new weight
  */
 public static function get_hiding_affected(&$grade_grades, &$grade_items)
 {
     global $CFG;
     if (count($grade_grades) !== count($grade_items)) {
         print_error('invalidarraysize', 'debug', '', 'grade_grade::get_hiding_affected()!');
     }
     $dependson = array();
     $todo = array();
     $unknown = array();
     // can not find altered
     $altered = array();
     // altered grades
     $alteredgrademax = array();
     // Altered grade max values.
     $alteredgrademin = array();
     // Altered grade min values.
     $alteredaggregationstatus = array();
     // Altered aggregation status.
     $alteredaggregationweight = array();
     // Altered aggregation weight.
     $dependencydepth = array();
     $hiddenfound = false;
     foreach ($grade_grades as $itemid => $unused) {
         $grade_grade =& $grade_grades[$itemid];
         // We need the immediate dependencies of all every grade_item so we can calculate nested dependencies.
         $dependson[$grade_grade->itemid] = $grade_items[$grade_grade->itemid]->depends_on();
         if ($grade_grade->is_excluded()) {
             //nothing to do, aggregation is ok
         } else {
             if ($grade_grade->is_hidden()) {
                 $hiddenfound = true;
                 $altered[$grade_grade->itemid] = null;
                 $alteredaggregationstatus[$grade_grade->itemid] = 'dropped';
                 $alteredaggregationweight[$grade_grade->itemid] = 0;
             } else {
                 if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
                     // no need to recalculate locked or overridden grades
                 } else {
                     if (!empty($dependson[$grade_grade->itemid])) {
                         $dependencydepth[$grade_grade->itemid] = 1;
                         $todo[] = $grade_grade->itemid;
                     }
                 }
             }
         }
     }
     // Flatten the dependency tree and count number of branches to each leaf.
     self::flatten_dependencies_array($dependson, $dependencydepth);
     if (!$hiddenfound) {
         return array('unknown' => array(), 'altered' => array(), 'alteredgrademax' => array(), 'alteredgrademin' => array(), 'alteredaggregationstatus' => array(), 'alteredaggregationweight' => array());
     }
     // This line ensures that $dependencydepth has the same number of items as $todo.
     $dependencydepth = array_intersect_key($dependencydepth, array_flip($todo));
     // We need to resort the todo list by the dependency depth. This guarantees we process the leaves, then the branches.
     array_multisort($dependencydepth, $todo);
     $max = count($todo);
     $hidden_precursors = null;
     for ($i = 0; $i < $max; $i++) {
         $found = false;
         foreach ($todo as $key => $do) {
             $hidden_precursors = array_intersect($dependson[$do], $unknown);
             if ($hidden_precursors) {
                 // this item depends on hidden grade indirectly
                 $unknown[$do] = $do;
                 unset($todo[$key]);
                 $found = true;
                 continue;
             } else {
                 if (!array_intersect($dependson[$do], $todo)) {
                     $hidden_precursors = array_intersect($dependson[$do], array_keys($altered));
                     if (!$hidden_precursors) {
                         // hiding does not affect this grade
                         unset($todo[$key]);
                         $found = true;
                         continue;
                     } else {
                         // depends on altered grades - we should try to recalculate if possible
                         if ($grade_items[$do]->is_calculated() or !$grade_items[$do]->is_category_item() and !$grade_items[$do]->is_course_item()) {
                             // This is a grade item that is not a category or course and has been affected by grade hiding.
                             // I guess this means it is a calculation that needs to be recalculated.
                             $unknown[$do] = $do;
                             unset($todo[$key]);
                             $found = true;
                             continue;
                         } else {
                             // This is a grade category (or course).
                             $grade_category = $grade_items[$do]->load_item_category();
                             // Build a new list of the grades in this category.
                             $values = array();
                             $immediatedepends = $grade_items[$do]->depends_on();
                             foreach ($immediatedepends as $itemid) {
                                 if (array_key_exists($itemid, $altered)) {
                                     //nulling an altered precursor
                                     $values[$itemid] = $altered[$itemid];
                                     if (is_null($values[$itemid])) {
                                         // This means this was a hidden grade item removed from the result.
                                         unset($values[$itemid]);
                                     }
                                 } elseif (empty($values[$itemid])) {
                                     $values[$itemid] = $grade_grades[$itemid]->finalgrade;
                                 }
                             }
                             foreach ($values as $itemid => $value) {
                                 if ($grade_grades[$itemid]->is_excluded()) {
                                     unset($values[$itemid]);
                                     $alteredaggregationstatus[$itemid] = 'excluded';
                                     $alteredaggregationweight[$itemid] = null;
                                     continue;
                                 }
                                 // The grade min/max may have been altered by hiding.
                                 $grademin = $grade_items[$itemid]->grademin;
                                 if (isset($alteredgrademin[$itemid])) {
                                     $grademin = $alteredgrademin[$itemid];
                                 }
                                 $grademax = $grade_items[$itemid]->grademax;
                                 if (isset($alteredgrademax[$itemid])) {
                                     $grademax = $alteredgrademax[$itemid];
                                 }
                                 $values[$itemid] = grade_grade::standardise_score($value, $grademin, $grademax, 0, 1);
                             }
                             if ($grade_category->aggregateonlygraded) {
                                 foreach ($values as $itemid => $value) {
                                     if (is_null($value)) {
                                         unset($values[$itemid]);
                                         $alteredaggregationstatus[$itemid] = 'novalue';
                                         $alteredaggregationweight[$itemid] = null;
                                     }
                                 }
                             } else {
                                 foreach ($values as $itemid => $value) {
                                     if (is_null($value)) {
                                         $values[$itemid] = 0;
                                     }
                                 }
                             }
                             // limit and sort
                             $allvalues = $values;
                             $grade_category->apply_limit_rules($values, $grade_items);
                             $moredropped = array_diff($allvalues, $values);
                             foreach ($moredropped as $drop => $unused) {
                                 $alteredaggregationstatus[$drop] = 'dropped';
                                 $alteredaggregationweight[$drop] = null;
                             }
                             foreach ($values as $itemid => $val) {
                                 if ($grade_category->is_extracredit_used() && $grade_items[$itemid]->aggregationcoef > 0) {
                                     $alteredaggregationstatus[$itemid] = 'extra';
                                 }
                             }
                             asort($values, SORT_NUMERIC);
                             // let's see we have still enough grades to do any statistics
                             if (count($values) == 0) {
                                 // not enough attempts yet
                                 $altered[$do] = null;
                                 unset($todo[$key]);
                                 $found = true;
                                 continue;
                             }
                             $usedweights = array();
                             $adjustedgrade = $grade_category->aggregate_values_and_adjust_bounds($values, $grade_items, $usedweights);
                             // recalculate the rawgrade back to requested range
                             $finalgrade = grade_grade::standardise_score($adjustedgrade['grade'], 0, 1, $adjustedgrade['grademin'], $adjustedgrade['grademax']);
                             foreach ($usedweights as $itemid => $weight) {
                                 if (!isset($alteredaggregationstatus[$itemid])) {
                                     $alteredaggregationstatus[$itemid] = 'used';
                                 }
                                 $alteredaggregationweight[$itemid] = $weight;
                             }
                             $finalgrade = $grade_items[$do]->bounded_grade($finalgrade);
                             $alteredgrademin[$do] = $adjustedgrade['grademin'];
                             $alteredgrademax[$do] = $adjustedgrade['grademax'];
                             // We need to muck with the "in-memory" grade_items records so
                             // that subsequent calculations will use the adjusted grademin and grademax.
                             $grade_items[$do]->grademin = $adjustedgrade['grademin'];
                             $grade_items[$do]->grademax = $adjustedgrade['grademax'];
                             $altered[$do] = $finalgrade;
                             unset($todo[$key]);
                             $found = true;
                             continue;
                         }
                     }
                 }
             }
         }
         if (!$found) {
             break;
         }
     }
     return array('unknown' => $unknown, 'altered' => $altered, 'alteredgrademax' => $alteredgrademax, 'alteredgrademin' => $alteredgrademin, 'alteredaggregationstatus' => $alteredaggregationstatus, 'alteredaggregationweight' => $alteredaggregationweight);
 }
Пример #8
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;
 }
Пример #9
0
 function sub_test_grade_grade_standardise_score() {
     $this->assertEqual(4, round(grade_grade::standardise_score(6, 0, 7, 0, 5)));
     $this->assertEqual(40, grade_grade::standardise_score(50, 30, 80, 0, 100));
 }
Пример #10
0
/**
 * Returns a letter grade representation of a grade value
 * The array of grade letters used is produced by {@link grade_get_letters()} using the course context
 *
 * @param float $value The grade value
 * @param object $grade_item Grade item object
 * @return string
 */
function grade_format_gradevalue_letter($value, $grade_item)
{
    global $CFG;
    $context = context_course::instance($grade_item->courseid, IGNORE_MISSING);
    if (!($letters = grade_get_letters($context))) {
        return '';
        // no letters??
    }
    if (is_null($value)) {
        return '-';
    }
    $value = grade_grade::standardise_score($value, $grade_item->grademin, $grade_item->grademax, 0, 100);
    $value = bounded_number(0, $value, 100);
    // just in case
    $gradebookcalculationsfreeze = 'gradebook_calculations_freeze_' . $grade_item->courseid;
    foreach ($letters as $boundary => $letter) {
        if (property_exists($CFG, $gradebookcalculationsfreeze) && (int) $CFG->{$gradebookcalculationsfreeze} <= 20160518) {
            // Do nothing.
        } else {
            // The boundary is a percentage out of 100 so use 0 as the min and 100 as the max.
            $boundary = grade_grade::standardise_score($boundary, 0, 100, 0, 100);
        }
        if ($value >= $boundary) {
            return format_string($letter);
        }
    }
    return '-';
    // no match? maybe '' would be more correct
}
Пример #11
0
 /**
  * Given a float grade value or integer grade scale, applies a number of adjustment based on
  * grade_item variables and returns the result.
  *
  * @param float $rawgrade The raw grade value
  * @param float $rawmin original rawmin
  * @param float $rawmax original rawmax
  * @return mixed
  */
 public function adjust_raw_grade($rawgrade, $rawmin, $rawmax)
 {
     if (is_null($rawgrade)) {
         return null;
     }
     if ($this->gradetype == GRADE_TYPE_VALUE) {
         // Dealing with numerical grade
         if ($this->grademax < $this->grademin) {
             return null;
         }
         if ($this->grademax == $this->grademin) {
             return $this->grademax;
             // no range
         }
         // Standardise score to the new grade range
         // NOTE: skip if the activity provides a manual rescaling option.
         $manuallyrescale = component_callback_exists('mod_' . $this->itemmodule, 'rescale_activity_grades') !== false;
         if (!$manuallyrescale && ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
             $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
         }
         // Apply other grade_item factors
         $rawgrade *= $this->multfactor;
         $rawgrade += $this->plusfactor;
         return $this->bounded_grade($rawgrade);
     } else {
         if ($this->gradetype == GRADE_TYPE_SCALE) {
             // Dealing with a scale value
             if (empty($this->scale)) {
                 $this->load_scale();
             }
             if ($this->grademax < 0) {
                 return null;
                 // scale not present - no grade
             }
             if ($this->grademax == 0) {
                 return $this->grademax;
                 // only one option
             }
             // Convert scale if needed
             // NOTE: skip if the activity provides a manual rescaling option.
             $manuallyrescale = component_callback_exists('mod_' . $this->itemmodule, 'rescale_activity_grades') !== false;
             if (!$manuallyrescale && ($rawmin != $this->grademin or $rawmax != $this->grademax)) {
                 // This should never happen because scales are locked if they are in use.
                 $rawgrade = grade_grade::standardise_score($rawgrade, $rawmin, $rawmax, $this->grademin, $this->grademax);
             }
             return $this->bounded_grade($rawgrade);
         } else {
             if ($this->gradetype == GRADE_TYPE_TEXT or $this->gradetype == GRADE_TYPE_NONE) {
                 // no value
                 // somebody changed the grading type when grades already existed
                 return null;
             } else {
                 debugging("Unknown grade type");
                 return null;
             }
         }
     }
 }
Пример #12
0
 /**
  * Internal function that calculates the aggregated grade and new min/max for this grade category
  *
  * Must be public as it is used by grade_grade::get_hiding_affected()
  *
  * @param array $grade_values An array of values to be aggregated
  * @param array $items The array of grade_items
  * @since Moodle 2.6.5, 2.7.2
  * @param array & $weights If provided, will be filled with the normalized weights
  *                         for each grade_item as used in the aggregation.
  *                         Some rules for the weights are:
  *                         1. The weights must add up to 1 (unless there are extra credit)
  *                         2. The contributed points column must add up to the course
  *                         final grade and this column is calculated from these weights.
  * @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)
  * @return array containing values for:
  *                'grade' => the new calculated grade
  *                'grademin' => the new calculated min grade for the category
  *                'grademax' => the new calculated max grade for the category
  */
 public function aggregate_values_and_adjust_bounds($grade_values, $items, &$weights = null, $grademinoverrides = array(), $grademaxoverrides = array())
 {
     $category_item = $this->get_grade_item();
     $grademin = $category_item->grademin;
     $grademax = $category_item->grademax;
     switch ($this->aggregation) {
         case GRADE_AGGREGATE_MEDIAN:
             // Middle point value in the set: ignores frequencies
             $num = count($grade_values);
             $grades = array_values($grade_values);
             // The median gets 100% - others get 0.
             if ($weights !== null && $num > 0) {
                 $count = 0;
                 foreach ($grade_values as $itemid => $grade_value) {
                     if ($num % 2 == 0 && ($count == intval($num / 2) - 1 || $count == intval($num / 2))) {
                         $weights[$itemid] = 0.5;
                     } else {
                         if ($num % 2 != 0 && $count == intval($num / 2 - 0.5)) {
                             $weights[$itemid] = 1.0;
                         } else {
                             $weights[$itemid] = 0;
                         }
                     }
                     $count++;
                 }
             }
             if ($num % 2 == 0) {
                 $agg_grade = ($grades[intval($num / 2) - 1] + $grades[intval($num / 2)]) / 2;
             } else {
                 $agg_grade = $grades[intval($num / 2 - 0.5)];
             }
             break;
         case GRADE_AGGREGATE_MIN:
             $agg_grade = reset($grade_values);
             // Record the weights as used.
             if ($weights !== null) {
                 foreach ($grade_values as $itemid => $grade_value) {
                     $weights[$itemid] = 0;
                 }
             }
             // Set the first item to 1.
             $itemids = array_keys($grade_values);
             $weights[reset($itemids)] = 1;
             break;
         case GRADE_AGGREGATE_MAX:
             // Record the weights as used.
             if ($weights !== null) {
                 foreach ($grade_values as $itemid => $grade_value) {
                     $weights[$itemid] = 0;
                 }
             }
             // Set the last item to 1.
             $itemids = array_keys($grade_values);
             $weights[end($itemids)] = 1;
             $agg_grade = end($grade_values);
             break;
         case GRADE_AGGREGATE_MODE:
             // the most common value
             // array_count_values only counts INT and STRING, so if grades are floats we must convert them to string
             $converted_grade_values = array();
             foreach ($grade_values as $k => $gv) {
                 if (!is_int($gv) && !is_string($gv)) {
                     $converted_grade_values[$k] = (string) $gv;
                 } else {
                     $converted_grade_values[$k] = $gv;
                 }
                 if ($weights !== null) {
                     $weights[$k] = 0;
                 }
             }
             $freq = array_count_values($converted_grade_values);
             arsort($freq);
             // sort by frequency keeping keys
             $top = reset($freq);
             // highest frequency count
             $modes = array_keys($freq, $top);
             // search for all modes (have the same highest count)
             rsort($modes, SORT_NUMERIC);
             // get highest mode
             $agg_grade = reset($modes);
             // Record the weights as used.
             if ($weights !== null && $top > 0) {
                 foreach ($grade_values as $k => $gv) {
                     if ($gv == $agg_grade) {
                         $weights[$k] = 1.0 / $top;
                     }
                 }
             }
             break;
         case GRADE_AGGREGATE_WEIGHTED_MEAN:
             // Weighted average of all existing final grades, weight specified in coef
             $weightsum = 0;
             $sum = 0;
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($weights !== null) {
                     $weights[$itemid] = $items[$itemid]->aggregationcoef;
                 }
                 if ($items[$itemid]->aggregationcoef <= 0) {
                     continue;
                 }
                 $weightsum += $items[$itemid]->aggregationcoef;
                 $sum += $items[$itemid]->aggregationcoef * $grade_value;
             }
             if ($weightsum == 0) {
                 $agg_grade = null;
             } else {
                 $agg_grade = $sum / $weightsum;
                 if ($weights !== null) {
                     // Normalise the weights.
                     foreach ($weights as $itemid => $weight) {
                         $weights[$itemid] = $weight / $weightsum;
                     }
                 }
             }
             break;
         case GRADE_AGGREGATE_WEIGHTED_MEAN2:
             // Weighted average of all existing final grades with optional extra credit flag,
             // weight is the range of grade (usually grademax)
             $this->load_grade_item();
             $weightsum = 0;
             $sum = null;
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef > 0) {
                     continue;
                 }
                 $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
                 if ($weight <= 0) {
                     continue;
                 }
                 $weightsum += $weight;
                 $sum += $weight * $grade_value;
             }
             // Handle the extra credit items separately to calculate their weight accurately.
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef <= 0) {
                     continue;
                 }
                 $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
                 if ($weight <= 0) {
                     $weights[$itemid] = 0;
                     continue;
                 }
                 $oldsum = $sum;
                 $weightedgrade = $weight * $grade_value;
                 $sum += $weightedgrade;
                 if ($weights !== null) {
                     if ($weightsum <= 0) {
                         $weights[$itemid] = 0;
                         continue;
                     }
                     $oldgrade = $oldsum / $weightsum;
                     $grade = $sum / $weightsum;
                     $normoldgrade = grade_grade::standardise_score($oldgrade, 0, 1, $grademin, $grademax);
                     $normgrade = grade_grade::standardise_score($grade, 0, 1, $grademin, $grademax);
                     $boundedoldgrade = $this->grade_item->bounded_grade($normoldgrade);
                     $boundedgrade = $this->grade_item->bounded_grade($normgrade);
                     if ($boundedgrade - $boundedoldgrade <= 0) {
                         // Nothing new was added to the grade.
                         $weights[$itemid] = 0;
                     } else {
                         if ($boundedgrade < $normgrade) {
                             // The grade has been bounded, the extra credit item needs to have a different weight.
                             $gradediff = $boundedgrade - $normoldgrade;
                             $gradediffnorm = grade_grade::standardise_score($gradediff, $grademin, $grademax, 0, 1);
                             $weights[$itemid] = $gradediffnorm / $grade_value;
                         } else {
                             // Default weighting.
                             $weights[$itemid] = $weight / $weightsum;
                         }
                     }
                 }
             }
             if ($weightsum == 0) {
                 $agg_grade = $sum;
                 // only extra credits
             } else {
                 $agg_grade = $sum / $weightsum;
             }
             // Record the weights as used.
             if ($weights !== null) {
                 foreach ($grade_values as $itemid => $grade_value) {
                     if ($items[$itemid]->aggregationcoef > 0) {
                         // Ignore extra credit items, the weights have already been computed.
                         continue;
                     }
                     if ($weightsum > 0) {
                         $weight = $items[$itemid]->grademax - $items[$itemid]->grademin;
                         $weights[$itemid] = $weight / $weightsum;
                     } else {
                         $weights[$itemid] = 0;
                     }
                 }
             }
             break;
         case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
             // special average
             $this->load_grade_item();
             $num = 0;
             $sum = null;
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef == 0) {
                     $num += 1;
                     $sum += $grade_value;
                     if ($weights !== null) {
                         $weights[$itemid] = 1;
                     }
                 }
             }
             // Treating the extra credit items separately to get a chance to calculate their effective weights.
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef > 0) {
                     $oldsum = $sum;
                     $sum += $items[$itemid]->aggregationcoef * $grade_value;
                     if ($weights !== null) {
                         if ($num <= 0) {
                             // The category only contains extra credit items, not setting the weight.
                             continue;
                         }
                         $oldgrade = $oldsum / $num;
                         $grade = $sum / $num;
                         $normoldgrade = grade_grade::standardise_score($oldgrade, 0, 1, $grademin, $grademax);
                         $normgrade = grade_grade::standardise_score($grade, 0, 1, $grademin, $grademax);
                         $boundedoldgrade = $this->grade_item->bounded_grade($normoldgrade);
                         $boundedgrade = $this->grade_item->bounded_grade($normgrade);
                         if ($boundedgrade - $boundedoldgrade <= 0) {
                             // Nothing new was added to the grade.
                             $weights[$itemid] = 0;
                         } else {
                             if ($boundedgrade < $normgrade) {
                                 // The grade has been bounded, the extra credit item needs to have a different weight.
                                 $gradediff = $boundedgrade - $normoldgrade;
                                 $gradediffnorm = grade_grade::standardise_score($gradediff, $grademin, $grademax, 0, 1);
                                 $weights[$itemid] = $gradediffnorm / $grade_value;
                             } else {
                                 // Default weighting.
                                 $weights[$itemid] = 1.0 / $num;
                             }
                         }
                     }
                 }
             }
             if ($weights !== null && $num > 0) {
                 foreach ($grade_values as $itemid => $grade_value) {
                     if ($items[$itemid]->aggregationcoef > 0) {
                         // Extra credit weights were already calculated.
                         continue;
                     }
                     if ($weights[$itemid]) {
                         $weights[$itemid] = 1.0 / $num;
                     }
                 }
             }
             if ($num == 0) {
                 $agg_grade = $sum;
                 // only extra credits or wrong coefs
             } else {
                 $agg_grade = $sum / $num;
             }
             break;
         case GRADE_AGGREGATE_SUM:
             // Add up all the items.
             $this->load_grade_item();
             $num = count($grade_values);
             $sum = 0;
             // This setting indicates if we should use algorithm prior to MDL-49257 fix for calculating extra credit weights.
             // Even though old algorith has bugs in it, we need to preserve existing grades.
             $gradebookcalculationfreeze = (int) get_config('core', 'gradebook_calculations_freeze_' . $this->courseid);
             $oldextracreditcalculation = $gradebookcalculationfreeze && $gradebookcalculationfreeze <= 20150619;
             $sumweights = 0;
             $grademin = 0;
             $grademax = 0;
             $extracredititems = array();
             foreach ($grade_values as $itemid => $gradevalue) {
                 // We need to check if the grademax/min was adjusted per user because of excluded items.
                 $usergrademin = $items[$itemid]->grademin;
                 $usergrademax = $items[$itemid]->grademax;
                 if (isset($grademinoverrides[$itemid])) {
                     $usergrademin = $grademinoverrides[$itemid];
                 }
                 if (isset($grademaxoverrides[$itemid])) {
                     $usergrademax = $grademaxoverrides[$itemid];
                 }
                 // Keep track of the extra credit items, we will need them later on.
                 if ($items[$itemid]->aggregationcoef > 0) {
                     $extracredititems[$itemid] = $items[$itemid];
                 }
                 // Ignore extra credit and items with a weight of 0.
                 if (!isset($extracredititems[$itemid]) && $items[$itemid]->aggregationcoef2 > 0) {
                     $grademin += $usergrademin;
                     $grademax += $usergrademax;
                     $sumweights += $items[$itemid]->aggregationcoef2;
                 }
             }
             $userweights = array();
             $totaloverriddenweight = 0;
             $totaloverriddengrademax = 0;
             // We first need to rescale all manually assigned weights down by the
             // percentage of weights missing from the category.
             foreach ($grade_values as $itemid => $gradevalue) {
                 if ($items[$itemid]->weightoverride) {
                     if ($items[$itemid]->aggregationcoef2 <= 0) {
                         // Records the weight of 0 and continue.
                         $userweights[$itemid] = 0;
                         continue;
                     }
                     $userweights[$itemid] = $sumweights ? $items[$itemid]->aggregationcoef2 / $sumweights : 0;
                     if (!$oldextracreditcalculation && isset($extracredititems[$itemid])) {
                         // Extra credit items do not affect totals.
                         continue;
                     }
                     $totaloverriddenweight += $userweights[$itemid];
                     $usergrademax = $items[$itemid]->grademax;
                     if (isset($grademaxoverrides[$itemid])) {
                         $usergrademax = $grademaxoverrides[$itemid];
                     }
                     $totaloverriddengrademax += $usergrademax;
                 }
             }
             $nonoverriddenpoints = $grademax - $totaloverriddengrademax;
             // Then we need to recalculate the automatic weights except for extra credit items.
             foreach ($grade_values as $itemid => $gradevalue) {
                 if (!$items[$itemid]->weightoverride && ($oldextracreditcalculation || !isset($extracredititems[$itemid]))) {
                     $usergrademax = $items[$itemid]->grademax;
                     if (isset($grademaxoverrides[$itemid])) {
                         $usergrademax = $grademaxoverrides[$itemid];
                     }
                     if ($nonoverriddenpoints > 0) {
                         $userweights[$itemid] = $usergrademax / $nonoverriddenpoints * (1 - $totaloverriddenweight);
                     } else {
                         $userweights[$itemid] = 0;
                         if ($items[$itemid]->aggregationcoef2 > 0) {
                             // Items with a weight of 0 should not count for the grade max,
                             // though this only applies if the weight was changed to 0.
                             $grademax -= $usergrademax;
                         }
                     }
                 }
             }
             // Now when we finally know the grademax we can adjust the automatic weights of extra credit items.
             if (!$oldextracreditcalculation) {
                 foreach ($grade_values as $itemid => $gradevalue) {
                     if (!$items[$itemid]->weightoverride && isset($extracredititems[$itemid])) {
                         $usergrademax = $items[$itemid]->grademax;
                         if (isset($grademaxoverrides[$itemid])) {
                             $usergrademax = $grademaxoverrides[$itemid];
                         }
                         $userweights[$itemid] = $grademax ? $usergrademax / $grademax : 0;
                     }
                 }
             }
             // We can use our freshly corrected weights below.
             foreach ($grade_values as $itemid => $gradevalue) {
                 if (isset($extracredititems[$itemid])) {
                     // We skip the extra credit items first.
                     continue;
                 }
                 $sum += $gradevalue * $userweights[$itemid] * $grademax;
                 if ($weights !== null) {
                     $weights[$itemid] = $userweights[$itemid];
                 }
             }
             // No we proceed with the extra credit items. They might have a different final
             // weight in case the final grade was bounded. So we need to treat them different.
             // Also, as we need to use the bounded_grade() method, we have to inject the
             // right values there, and restore them afterwards.
             $oldgrademax = $this->grade_item->grademax;
             $oldgrademin = $this->grade_item->grademin;
             foreach ($grade_values as $itemid => $gradevalue) {
                 if (!isset($extracredititems[$itemid])) {
                     continue;
                 }
                 $oldsum = $sum;
                 $weightedgrade = $gradevalue * $userweights[$itemid] * $grademax;
                 $sum += $weightedgrade;
                 // Only go through this when we need to record the weights.
                 if ($weights !== null) {
                     if ($grademax <= 0) {
                         // There are only extra credit items in this category,
                         // all the weights should be accurate (and be 0).
                         $weights[$itemid] = $userweights[$itemid];
                         continue;
                     }
                     $oldfinalgrade = $this->grade_item->bounded_grade($oldsum);
                     $newfinalgrade = $this->grade_item->bounded_grade($sum);
                     $finalgradediff = $newfinalgrade - $oldfinalgrade;
                     if ($finalgradediff <= 0) {
                         // This item did not contribute to the category total at all.
                         $weights[$itemid] = 0;
                     } else {
                         if ($finalgradediff < $weightedgrade) {
                             // The weight needs to be adjusted because only a portion of the
                             // extra credit item contributed to the category total.
                             $weights[$itemid] = $finalgradediff / ($gradevalue * $grademax);
                         } else {
                             // The weight was accurate.
                             $weights[$itemid] = $userweights[$itemid];
                         }
                     }
                 }
             }
             $this->grade_item->grademax = $oldgrademax;
             $this->grade_item->grademin = $oldgrademin;
             if ($grademax > 0) {
                 $agg_grade = $sum / $grademax;
                 // Re-normalize score.
             } else {
                 // Every item in the category is extra credit.
                 $agg_grade = $sum;
                 $grademax = $sum;
             }
             break;
         case GRADE_AGGREGATE_MEAN:
             // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
         // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
         default:
             $num = count($grade_values);
             $sum = array_sum($grade_values);
             $agg_grade = $sum / $num;
             // Record the weights evenly.
             if ($weights !== null && $num > 0) {
                 foreach ($grade_values as $itemid => $grade_value) {
                     $weights[$itemid] = 1.0 / $num;
                 }
             }
             break;
     }
     return array('grade' => $agg_grade, 'grademin' => $grademin, 'grademax' => $grademax);
 }
 /**
  * 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;
 }
Пример #14
0
 /**
  * internal function for category grades aggregation
  */
 function aggregate_grades($userid, $items, $grade_values, $oldgrade, $excluded)
 {
     global $CFG;
     if (empty($userid)) {
         //ignore first call
         return;
     }
     if ($oldgrade) {
         $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->insert('system');
         $grade->grade_item =& $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;
     }
     // 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]);
     // if no grades calculation possible or grading not allowed clear both final and raw
     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;
         $grade->rawgrade = null;
         if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade) {
             $grade->update('system');
         }
         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);
     }
     // If global aggregateonlygraded is set, override category value
     if ($CFG->grade_aggregateonlygraded != -1) {
         $this->aggregateonlygraded = $CFG->grade_aggregateonlygraded;
     }
     // 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);
     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;
         $grade->rawgrade = null;
         if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade) {
             $grade->update('system');
         }
         return;
     }
     /// start the aggregation
     switch ($this->aggregation) {
         case GRADE_AGGREGATE_MEDIAN:
             // Middle point value in the set: ignores frequencies
             $num = count($grade_values);
             $grades = array_values($grade_values);
             if ($num % 2 == 0) {
                 $agg_grade = ($grades[intval($num / 2) - 1] + $grades[intval($num / 2)]) / 2;
             } else {
                 $agg_grade = $grades[intval($num / 2 - 0.5)];
             }
             break;
         case GRADE_AGGREGATE_MIN:
             $agg_grade = reset($grade_values);
             break;
         case GRADE_AGGREGATE_MAX:
             $agg_grade = array_pop($grade_values);
             break;
         case GRADE_AGGREGATE_MODE:
             // the most common value, average used if multimode
             $freq = array_count_values($grade_values);
             arsort($freq);
             // sort by frequency keeping keys
             $top = reset($freq);
             // highest frequency count
             $modes = array_keys($freq, $top);
             // search for all modes (have the same highest count)
             rsort($modes, SORT_NUMERIC);
             // get highes mode
             $agg_grade = reset($modes);
             break;
         case GRADE_AGGREGATE_WEIGHTED_MEAN:
             // Weighted average of all existing final grades
             $weightsum = 0;
             $sum = 0;
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef <= 0) {
                     continue;
                 }
                 $weightsum += $items[$itemid]->aggregationcoef;
                 $sum += $items[$itemid]->aggregationcoef * $grade_value;
             }
             if ($weightsum == 0) {
                 $agg_grade = null;
             } else {
                 $agg_grade = $sum / $weightsum;
             }
             break;
         case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
             // special average
             $num = 0;
             $sum = 0;
             foreach ($grade_values as $itemid => $grade_value) {
                 if ($items[$itemid]->aggregationcoef == 0) {
                     $num += 1;
                     $sum += $grade_value;
                 } else {
                     if ($items[$itemid]->aggregationcoef > 0) {
                         $sum += $items[$itemid]->aggregationcoef * $grade_value;
                     }
                 }
             }
             if ($num == 0) {
                 $agg_grade = $sum;
                 // only extra credits or wrong coefs
             } else {
                 $agg_grade = $sum / $num;
             }
             break;
         case GRADE_AGGREGATE_MEAN:
             // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
         // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
         default:
             $num = count($grade_values);
             $sum = array_sum($grade_values);
             $agg_grade = $sum / $num;
             break;
     }
     /// prepare update of new raw grade
     $grade->rawgrademin = $this->grade_item->grademin;
     $grade->rawgrademax = $this->grade_item->grademax;
     $grade->rawscaleid = $this->grade_item->scaleid;
     $grade->rawgrade = null;
     // categories do not use raw grades
     // recalculate the rawgrade back to requested range
     $finalgrade = grade_grade::standardise_score($agg_grade, 0, 1, $this->grade_item->grademin, $this->grade_item->grademax);
     if (!is_null($finalgrade)) {
         $grade->finalgrade = bounded_number($this->grade_item->grademin, $finalgrade, $this->grade_item->grademax);
     } else {
         $grade->finalgrade = $finalgrade;
     }
     // update in db if changed
     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) {
         $grade->update('system');
     }
     return;
 }