diff --git a/grade/edit/tree/category.php b/grade/edit/tree/category.php index b30f948bf82..05c7c417e98 100644 --- a/grade/edit/tree/category.php +++ b/grade/edit/tree/category.php @@ -51,7 +51,7 @@ if ($id) { error('Incorrect category id!'); } $grade_category->apply_forced_settings(); - $category = $grade_category->get_record_data(); + $category = $grade_category->get_record_data(); // Get Category preferences $category->pref_aggregationview = grade_report::get_pref('aggregationview', $id); @@ -59,7 +59,7 @@ if ($id) { $grade_category = new grade_category(); $grade_category->courseid = $course->id; $grade_category->apply_forced_settings(); - $category = $grade_category->get_record_data(); + $category = $grade_category->get_record_data(); } $mform->set_data($category); @@ -68,6 +68,12 @@ if ($mform->is_cancelled()) { redirect($returnurl); } else if ($data = $mform->get_data(false)) { + if (!isset($data->aggregateonlygraded)) { + $data->aggregateonlygraded = 0; + } + if (!isset($data->aggregateoutcomes)) { + $data->aggregateoutcomes = 0; + } grade_category::set_properties($grade_category, $data); if (empty($grade_category->id)) { diff --git a/grade/edit/tree/category_form.php b/grade/edit/tree/category_form.php index ceb14a8706f..234a74a4af1 100644 --- a/grade/edit/tree/category_form.php +++ b/grade/edit/tree/category_form.php @@ -37,24 +37,30 @@ class edit_category_form extends moodleform { GRADE_AGGREGATE_MAX =>get_string('aggregatemax', 'grades'), GRADE_AGGREGATE_MODE =>get_string('aggregatemode', 'grades'), GRADE_AGGREGATE_WEIGHTED_MEAN =>get_string('aggregateweightedmean', 'grades'), - GRADE_AGGREGATE_EXTRACREDIT_MEAN=>get_string('aggregateextracreditmean', 'grades')); + GRADE_AGGREGATE_EXTRACREDIT_MEAN=>get_string('aggregateextracreditmean', 'grades'), + GRADE_AGGREGATE_SUM =>get_string('aggregatesum', 'grades')); // visible elements $mform->addElement('header', 'gradecat', get_string('gradecategory', 'grades')); $mform->addElement('text', 'fullname', get_string('categoryname', 'grades')); $mform->addRule('fullname', null, 'required', null, 'client'); - + $mform->addElement('select', 'aggregation', get_string('aggregation', 'grades'), $options); $mform->setHelpButton('aggregation', array('aggregation', get_string('aggregation', 'grades'), 'grade')); - $mform->addElement('advcheckbox', 'aggregateonlygraded', get_string('aggregateonlygraded', 'grades')); + $mform->addElement('checkbox', 'aggregateonlygraded', get_string('aggregateonlygraded', 'grades')); $mform->setHelpButton('aggregateonlygraded', array(false, get_string('aggregateonlygraded', 'grades'), false, true, false, get_string('aggregateonlygradedhelp', 'grades'))); + $mform->disabledIf('aggregateonlygraded', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); - if (!empty($CFG->enableoutcomes)) { - $mform->addElement('advcheckbox', 'aggregateoutcomes', get_string('aggregateoutcomes', 'grades')); + if (empty($CFG->enableoutcomes)) { + $mform->addElement('hidden', 'aggregateoutcomes'); + $mform->setType('aggregateoutcomes', PARAM_INT); + } else { + $mform->addElement('checkbox', 'aggregateoutcomes', get_string('aggregateoutcomes', 'grades')); $mform->setHelpButton('aggregateoutcomes', array(false, get_string('aggregateoutcomes', 'grades'), false, true, false, get_string('aggregateoutcomeshelp', 'grades'))); + $mform->disabledIf('aggregateoutcomes', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); } $mform->addElement('advcheckbox', 'aggregatesubcats', get_string('aggregatesubcats', 'grades')); diff --git a/grade/edit/tree/item_form.php b/grade/edit/tree/item_form.php index ed952d9ec10..3d4838a8e61 100644 --- a/grade/edit/tree/item_form.php +++ b/grade/edit/tree/item_form.php @@ -197,6 +197,7 @@ class edit_item_form extends moodleform { //$mform->removeElement('calculation'); } } + //remove the aggregation coef element if not needed if ($grade_item->is_course_item()) { $mform->removeElement('aggregationcoef'); @@ -227,6 +228,23 @@ class edit_item_form extends moodleform { } } + if ($category = $grade_item->get_item_category()) { + if ($category->aggregation == GRADE_AGGREGATE_SUM) { + if ($mform->elementExists('gradetype')) { + $mform->hardFreeze('gradetype'); + } + if ($mform->elementExists('grademin')) { + $mform->hardFreeze('grademin'); + } + if ($mform->elementExists('grademax')) { + $mform->hardFreeze('grademax'); + } + if ($mform->elementExists('scaleid')) { + $mform->removeElement('scaleid'); + } + } + } + } else { // all new items are manual, children of course category $mform->removeElement('plusfactor'); diff --git a/grade/lib.php b/grade/lib.php index bc2cb67e955..321558a075b 100644 --- a/grade/lib.php +++ b/grade/lib.php @@ -740,8 +740,8 @@ class grade_structure { case GRADE_AGGREGATE_WEIGHTED_MEAN: case GRADE_AGGREGATE_EXTRACREDIT_MEAN: return ''.get_string('aggregation', 'grades').''; - //case GRADE_AGGREGATE_SUM: - //return ''.get_string('aggregation', 'grades').''; + case GRADE_AGGREGATE_SUM: + return ''.get_string('aggregation', 'grades').''; } } diff --git a/lang/en_utf8/grades.php b/lang/en_utf8/grades.php index 2bf9c64c48b..b57e4c4ee1a 100644 --- a/lang/en_utf8/grades.php +++ b/lang/en_utf8/grades.php @@ -23,6 +23,7 @@ $string['aggregateoutcomes'] = 'Include outcomes in aggregation'; $string['aggregateoutcomeshelp'] = 'Including outcomes in aggregation may not lead to the desired overall grade, so you have the option to include or leave them out.'; $string['aggregatesubcats'] = 'Aggregate including subcategories'; $string['aggregatesubcatshelp'] = 'The aggregation is usually done only with immediate children, it is also possible to aggregate grades in all subcategories excluding other aggregated grades.'; +$string['aggregatesum'] = 'Sum of grades'; $string['aggregatesonly'] = 'Aggregates only'; $string['aggregateweightedmean'] = 'Weighted mean of grades'; $string['aggregation'] = 'Aggregation'; diff --git a/lib/grade/constants.php b/lib/grade/constants.php index fabb36dc63f..b35a45c02d2 100644 --- a/lib/grade/constants.php +++ b/lib/grade/constants.php @@ -40,6 +40,7 @@ define('GRADE_AGGREGATE_MAX', 6); define('GRADE_AGGREGATE_MODE', 8); define('GRADE_AGGREGATE_WEIGHTED_MEAN', 10); define('GRADE_AGGREGATE_EXTRACREDIT_MEAN', 12); +define('GRADE_AGGREGATE_SUM', 13); // grade types define('GRADE_TYPE_NONE', 0); diff --git a/lib/grade/grade_category.php b/lib/grade/grade_category.php index a5165beb1e8..0b31735d889 100644 --- a/lib/grade/grade_category.php +++ b/lib/grade/grade_category.php @@ -375,9 +375,9 @@ class grade_category extends grade_object { } /** - * Generates and saves raw_grades in associated category grade item. + * Generates and saves final grades in associated category grade item. * These immediate children must already have their own final grades. - * The category's aggregation method is used to generate raw grades. + * The category's aggregation method is used to generate final grades. * * Please note that category grade is either calculated or aggregated - not both at the same time. * @@ -387,7 +387,7 @@ class grade_category extends grade_object { * Steps to follow: * 1. Get final grades from immediate children * 3. Aggregate these grades - * 4. Save them in raw grades of associated category grade item + * 4. Save them in final grades of associated category grade item */ function generate_grades($userid=null) { global $CFG; @@ -474,6 +474,10 @@ class grade_category extends grade_object { } if ($oldgrade) { + if (!is_null($oldgrade->finalgrade)) { + // we need proper floats here for !== comparison later + $oldgrade->finalgrade = (float)$oldgrade->finalgrade; + } $grade = $this->get_instance('grade_grade', $oldgrade, false); $grade->grade_item =& $this->grade_item; @@ -485,10 +489,6 @@ class grade_category extends grade_object { $oldgrade = new object(); $oldgrade->finalgrade = $grade->finalgrade; - $oldgrade->rawgrade = $grade->rawgrade; - $oldgrade->rawgrademin = $grade->rawgrademin; - $oldgrade->rawgrademax = $grade->rawgrademax; - $oldgrade->rawscaleid = $grade->rawscaleid; } $grade->itemid = $this->grade_item->id; // For testability, avoids the need for load_grade_item() @@ -501,15 +501,20 @@ class grade_category extends grade_object { // 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 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; - $grade->rawgrade = null; - if ($grade->finalgrade !== $oldgrade->finalgrade or $grade->rawgrade !== $oldgrade->rawgrade) { - $grade->update('system'); + if ($grade->finalgrade !== $oldgrade->finalgrade) { + $grade->update('aggregation'); } return; } + + /// 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, $oldgrade, $items, $grade_values, $excluded); + return; + } /// normalize the grades first - all will have value 0...1 // ungraded items are not used in aggregation foreach ($grade_values as $itemid=>$v) { @@ -541,9 +546,8 @@ class grade_category extends grade_object { 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'); + if ($grade->finalgrade !== $oldgrade->finalgrade) { + $grade->update('aggregation'); } return; } @@ -551,13 +555,7 @@ class grade_category extends grade_object { // do the maths $agg_grade = $this->aggregate_values($grade_values, $items); - /// 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 + // recalculate the grade 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)) { @@ -567,13 +565,8 @@ class grade_category extends grade_object { } // 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'); + if ($grade->finalgrade !== $oldgrade->finalgrade) { + $grade->update('aggregation'); } return; @@ -656,6 +649,58 @@ class grade_category extends grade_object { return $agg_grade; } + + + /** + * internal function for category grades summing + * + * @param int $userid + * @param array $items + * @param array $grade_values + * @param float $oldgrade + * @param bool $excluded + * @return boolean (just plain return;) + */ + function sum_grades(&$grade, &$oldgrade, $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]); + } + } + + $max = 0; + + //find max grade + foreach ($items as $item) { + if ($item->gradetype != GRADE_TYPE_VALUE) { + continue; // sum only items with value grades, no scales and outcomes! + } + $max += $item->grademax; + } + + 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->finalgrade !== $oldgrade->finalgrade) { + $grade->update('aggregation'); + } + + return; + } + /** * Given an array of grade values (numerical indices), applies droplow or keephigh * rules to limit the final array.