Merge branch 'wip-MDL-46576-master' of git://github.com/abgreeve/moodle

Conflicts:
	lib/upgrade.txt
    lib/db/upgrade.php
	version.php
This commit is contained in:
Dan Poltawski 2014-10-06 18:06:13 +01:00
commit f5bacd6539
52 changed files with 2991 additions and 1112 deletions

View File

@ -96,11 +96,9 @@ if (has_capability('moodle/grade:manage', $systemcontext)
GRADE_AGGREGATE_MODE =>new lang_string('aggregatemode', 'grades'),
GRADE_AGGREGATE_SUM =>new lang_string('aggregatesum', 'grades'));
$defaultvisible = array(GRADE_AGGREGATE_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN, GRADE_AGGREGATE_WEIGHTED_MEAN2,
GRADE_AGGREGATE_EXTRACREDIT_MEAN, GRADE_AGGREGATE_MEDIAN, GRADE_AGGREGATE_MIN,
GRADE_AGGREGATE_MAX, GRADE_AGGREGATE_MODE, GRADE_AGGREGATE_SUM);
$defaultvisible = array(GRADE_AGGREGATE_SUM);
$defaults = array('value'=>GRADE_AGGREGATE_WEIGHTED_MEAN2, 'forced'=>false, 'adv'=>false);
$defaults = array('value' => GRADE_AGGREGATE_SUM, 'forced' => false, 'adv' => false);
$temp->add(new admin_setting_gradecat_combo('grade_aggregation', new lang_string('aggregation', 'grades'), new lang_string('aggregation_help', 'grades'), $defaults, $options));
$temp->add(new admin_setting_configmultiselect('grade_aggregations_visible', new lang_string('aggregationsvisible', 'grades'),

View File

@ -940,8 +940,8 @@ class backup_gradebook_structure_step extends backup_structure_step {
'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
'calculation', 'gradetype', 'grademax', 'grademin',
'scaleid', 'outcomeid', 'gradepass', 'multfactor',
'plusfactor', 'aggregationcoef', 'sortorder', 'display',
'decimals', 'hidden', 'locked', 'locktime',
'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
'needsupdate', 'timecreated', 'timemodified'));
$grade_grades = new backup_nested_element('grade_grades');
@ -950,7 +950,8 @@ class backup_gradebook_structure_step extends backup_structure_step {
'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
'locked', 'locktime', 'exported', 'overridden',
'excluded', 'feedback', 'feedbackformat', 'information',
'informationformat', 'timecreated', 'timemodified'));
'informationformat', 'timecreated', 'timemodified',
'aggregationstatus', 'aggregationweight'));
//grade_categories
$grade_categories = new backup_nested_element('grade_categories');
@ -2163,8 +2164,8 @@ class backup_activity_grades_structure_step extends backup_structure_step {
'iteminstance', 'itemnumber', 'iteminfo', 'idnumber',
'calculation', 'gradetype', 'grademax', 'grademin',
'scaleid', 'outcomeid', 'gradepass', 'multfactor',
'plusfactor', 'aggregationcoef', 'sortorder', 'display',
'decimals', 'hidden', 'locked', 'locktime',
'plusfactor', 'aggregationcoef', 'aggregationcoef2', 'weightoverride',
'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
'needsupdate', 'timecreated', 'timemodified'));
$grades = new backup_nested_element('grade_grades');
@ -2174,7 +2175,8 @@ class backup_activity_grades_structure_step extends backup_structure_step {
'rawscaleid', 'usermodified', 'finalgrade', 'hidden',
'locked', 'locktime', 'exported', 'overridden',
'excluded', 'feedback', 'feedbackformat', 'information',
'informationformat', 'timecreated', 'timemodified'));
'informationformat', 'timecreated', 'timemodified',
'aggregationstatus', 'aggregationweight'));
$letters = new backup_nested_element('grade_letters');

View File

@ -32,6 +32,8 @@ $id = optional_param('id', 0, PARAM_INT);
$PAGE->set_url('/grade/edit/scale/edit.php', array('id' => $id, 'courseid' => $courseid));
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/grade/edit/scale/index.php',
array('id' => $courseid)));
$systemcontext = context_system::instance();
$heading = '';
@ -145,11 +147,7 @@ if ($mform->is_cancelled()) {
redirect($returnurl);
}
if ($courseid) {
print_grade_page_head($course->id, 'scale', 'edit', $heading);
} else {
echo $OUTPUT->header();
}
print_grade_page_head($COURSE->id, 'scale', null, $heading, false, false, false);
$mform->display();

View File

@ -45,8 +45,6 @@ $gpr = new grade_plugin_return(array('type'=>'edit', 'plugin'=>'settings', 'cour
$strgrades = get_string('grades');
$pagename = get_string('coursesettings', 'grades');
$navigation = grade_build_nav(__FILE__, $pagename, $courseid);
$returnurl = $CFG->wwwroot.'/grade/index.php?id='.$course->id;
$mform = new course_settings_form();
@ -76,7 +74,7 @@ if ($mform->is_cancelled()) {
redirect($returnurl);
}
print_grade_page_head($courseid, 'settings', 'coursesettings', get_string('coursesettings', 'grades'));
print_grade_page_head($courseid, 'settings', 'coursesettings', get_string('coursegradesettings', 'grades'));
echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthnormal centerpara');
echo get_string('coursesettingsexplanation', 'grades');

View File

@ -107,6 +107,37 @@ switch ($action) {
$object->set_locked(0, true, true);
}
break;
case 'resetweights':
if ($eid && confirm_sesskey()) {
// This is specific to category items with natural weight as an aggregation method, and can
// only be done by someone who can manage the grades.
if ($type != 'category' || $object->aggregation != GRADE_AGGREGATE_SUM ||
!has_capability('moodle/grade:manage', $context)) {
print_error('nopermissiontoresetweights', 'grades', $returnurl);
}
// Remove the weightoverride flag from the children.
$children = $object->get_children();
foreach ($children as $item) {
if ($item['type'] == 'category') {
$gradeitem = $item['object']->load_grade_item();
} else {
$gradeitem = $item['object'];
}
if ($gradeitem->weightoverride == false) {
continue;
}
$gradeitem->weightoverride = false;
$gradeitem->update();
}
// Force regrading.
$object->force_regrading();
}
}
redirect($returnurl);

View File

@ -46,6 +46,10 @@ require_login($course);
$context = context_course::instance($course->id);
require_capability('moodle/grade:manage', $context);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/grade/edit/tree/index.php',
array('id'=>$course->id)));
// default return url
$gpr = new grade_plugin_return();
$returnurl = $gpr->get_return_url($CFG->wwwroot.'/grade/report/index.php?id='.$course->id);
@ -109,10 +113,8 @@ $strgrades = get_string('grades');
$strgraderreport = get_string('graderreport', 'grades');
$strcalculationedit = get_string('editcalculation', 'grades');
grade_build_nav(__FILE__, $strcalculationedit, array('courseid' => $courseid));
$PAGE->set_title($strgrades . ': ' . $strgraderreport);
$PAGE->set_heading($course->fullname);
echo $OUTPUT->header();
$PAGE->navbar->add($strcalculationedit);
print_grade_page_head($courseid, 'settings', null, $strcalculationedit, false, false, false);
$mform->display();
// Now show the gradetree with the idnumbers add/edit form

View File

@ -36,6 +36,8 @@ if ($id !== 0) {
}
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/grade/edit/tree/index.php',
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
print_error('nocourseid');
@ -75,6 +77,7 @@ if ($id) {
$category->grade_item_gradepass = format_float($category->grade_item_gradepass, $decimalpoints);
$category->grade_item_multfactor = format_float($category->grade_item_multfactor, 4);
$category->grade_item_plusfactor = format_float($category->grade_item_plusfactor, 4);
$category->grade_item_aggregationcoef2 = format_float($category->grade_item_aggregationcoef2 * 100.0, 4);
if (!$parent_category) {
// keep as is
@ -84,6 +87,16 @@ if ($id) {
$category->grade_item_aggregationcoef = format_float($category->grade_item_aggregationcoef, 4);
}
if ($category->aggregation == GRADE_AGGREGATE_SUM) {
// Input fields for grademin and grademax are disabled for the "Natural" category,
// this means they will be ignored if user does not change aggregation method.
// But if user does change aggregation method the default values should be used.
$category->grademax = 100;
$category->grade_item_grademax = 100;
$category->grademin = 0;
$category->grade_item_grademin = 0;
}
} else {
$heading = get_string('newcategory', 'grades');
$grade_category = new grade_category(array('courseid'=>$courseid), false);
@ -160,12 +173,15 @@ if ($mform->is_cancelled()) {
unset($itemdata->locked);
unset($itemdata->locktime);
$convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef');
$convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef', 'aggregationcoef2');
foreach ($convert as $param) {
if (property_exists($itemdata, $param)) {
$itemdata->$param = unformat_float($itemdata->$param);
}
}
if (isset($itemdata->aggregationcoef2)) {
$itemdata->aggregationcoef2 = $itemdata->aggregationcoef2 / 100.0;
}
// When creating a new category, a number of grade item fields are filled out automatically, and are required.
// If the user leaves these fields empty during creation of a category, we let the default values take effect
@ -195,6 +211,14 @@ if ($mform->is_cancelled()) {
$grade_item->decimals = null;
}
// Change weightoverride flag. Check if the value is set, because it is not when the checkbox is not ticked.
$itemdata->weightoverride = isset($itemdata->weightoverride) ? $itemdata->weightoverride : 0;
if ($grade_item->weightoverride != $itemdata->weightoverride && $grade_category->aggregation == GRADE_AGGREGATE_SUM) {
// If we are using natural weight and the weight has been un-overriden, force parent category to recalculate weights.
$grade_category->force_regrading();
}
$grade_item->weightoverride = $itemdata->weightoverride;
$grade_item->outcomeid = null;
// update hiding flag
@ -217,10 +241,8 @@ if ($mform->is_cancelled()) {
redirect($returnurl);
}
$return = false;
$buttons = false;
$shownavigation = false;
print_grade_page_head($courseid, 'edittree', null, $heading, $return, $buttons, $shownavigation);
$PAGE->navbar->add($heading);
print_grade_page_head($courseid, 'settings', null, $heading, false, false, false);
$mform->display();

View File

@ -37,15 +37,7 @@ class edit_category_form extends moodleform {
$category = $this->_customdata['current'];
$this->aggregation_options = array(GRADE_AGGREGATE_MEAN =>get_string('aggregatemean', 'grades'),
GRADE_AGGREGATE_WEIGHTED_MEAN =>get_string('aggregateweightedmean', 'grades'),
GRADE_AGGREGATE_WEIGHTED_MEAN2 =>get_string('aggregateweightedmean2', 'grades'),
GRADE_AGGREGATE_EXTRACREDIT_MEAN=>get_string('aggregateextracreditmean', 'grades'),
GRADE_AGGREGATE_MEDIAN =>get_string('aggregatemedian', 'grades'),
GRADE_AGGREGATE_MIN =>get_string('aggregatemin', 'grades'),
GRADE_AGGREGATE_MAX =>get_string('aggregatemax', 'grades'),
GRADE_AGGREGATE_MODE =>get_string('aggregatemode', 'grades'),
GRADE_AGGREGATE_SUM =>get_string('aggregatesum', 'grades'));
$this->aggregation_options = grade_helper::get_aggregation_strings();
// visible elements
$mform->addElement('header', 'headercategory', get_string('gradecategory', 'grades'));
@ -62,7 +54,6 @@ class edit_category_form extends moodleform {
$mform->addElement('checkbox', 'aggregateonlygraded', get_string('aggregateonlygraded', 'grades'));
$mform->addHelpButton('aggregateonlygraded', 'aggregateonlygraded', 'grades');
$mform->disabledIf('aggregateonlygraded', 'aggregation', 'eq', GRADE_AGGREGATE_SUM);
if ((int)$CFG->grade_aggregateonlygraded_flag & 2) {
$mform->setAdvanced('aggregateonlygraded');
@ -170,6 +161,14 @@ class edit_category_form extends moodleform {
$mform->disabledIf('grade_item_grademin', 'aggregation', 'eq', GRADE_AGGREGATE_SUM);
}
$mform->addElement('advcheckbox', 'grade_item_weightoverride', get_string('adjustedweight', 'grades'));
$mform->addHelpButton('grade_item_weightoverride', 'weightoverride', 'grades');
$mform->addElement('text', 'grade_item_aggregationcoef2', get_string('weight', 'grades'));
$mform->addHelpButton('grade_item_aggregationcoef2', 'weight', 'grades');
$mform->setType('grade_item_aggregationcoef2', PARAM_RAW);
$mform->disabledIf('grade_item_aggregationcoef2', 'grade_item_weightoverride');
$mform->addElement('text', 'grade_item_gradepass', get_string('gradepass', 'grades'));
$mform->setType('grade_item_gradepass', PARAM_RAW);
$mform->addHelpButton('grade_item_gradepass', 'gradepass', 'grades');
@ -332,6 +331,18 @@ class edit_category_form extends moodleform {
}
// Prevent the user from using drop lowest/keep highest when the aggregation method cannot handle it.
if (!$grade_category->can_apply_limit_rules()) {
if ($mform->elementExists('keephigh')) {
$mform->setConstant('keephigh', 0);
$mform->hardFreeze('keephigh');
}
if ($mform->elementExists('droplow')) {
$mform->setConstant('droplow', 0);
$mform->hardFreeze('droplow');
}
}
if ($grade_item->is_calculated()) {
// following elements are ignored when calculation formula used
if ($mform->elementExists('aggregation')) {
@ -430,6 +441,12 @@ class edit_category_form extends moodleform {
$mform->removeElement('grade_item_aggregationcoef');
}
if ($mform->elementExists('grade_item_weightoverride')) {
$mform->removeElement('grade_item_weightoverride');
}
if ($mform->elementExists('grade_item_aggregationcoef2')) {
$mform->removeElement('grade_item_aggregationcoef2');
}
} else {
if ($grade_item->is_category_item()) {
$category = $grade_item->get_item_category();
@ -448,8 +465,9 @@ class edit_category_form extends moodleform {
$coefstring = $grade_item->get_coefstring();
if ($coefstring == 'aggregationcoefextrasum') {
if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') {
// advcheckbox is not compatible with disabledIf!
$coefstring = 'aggregationcoefextrasum';
$element =& $mform->createElement('checkbox', 'grade_item_aggregationcoef', get_string($coefstring, 'grades'));
} else {
$element =& $mform->createElement('text', 'grade_item_aggregationcoef', get_string($coefstring, 'grades'));
@ -457,6 +475,16 @@ class edit_category_form extends moodleform {
$mform->insertElementBefore($element, 'parentcategory');
$mform->addHelpButton('grade_item_aggregationcoef', $coefstring, 'grades');
}
// Remove fields used by natural weighting if the parent category is not using natural weighting.
if ($parent_category->aggregation != GRADE_AGGREGATE_SUM) {
if ($mform->elementExists('grade_item_weightoverride')) {
$mform->removeElement('grade_item_weightoverride');
}
if ($mform->elementExists('grade_item_aggregationcoef2')) {
$mform->removeElement('grade_item_aggregationcoef2');
}
}
}
}
}

View File

@ -69,6 +69,18 @@ function update_category_aggregation(e, args) {
window.location = 'index.php?id='+args.courseid+'&category='+args.category+'&aggregationtype='+selectmenu.get('value')+'&sesskey='+args.sesskey;
}
/**
* The weight override checkboxes toggle the disabled status of their associated weight fields.
*/
YUI().use('node', 'delegate', function(Y) {
Y.on('domready', function() {
Y.delegate('click', function(e) {
var t = e.currentTarget,
itemid = t.get('id').split('_')[1];
Y.one('input[name=weight_' + itemid + ']').set('disabled', t.get('checked') ? false : true);
}, Y.config.doc.body, 'input.weightoverride');
});
});
/* TODO: finish and rewrite for YUI3...
Y.YUI2.namespace('grade_edit_tree');

View File

@ -85,11 +85,7 @@ class edit_grade_form extends moodleform {
$mform->disabledIf('finalgrade', 'overridden', 'notchecked');
}
if ($grade_category and $grade_category->aggregation == GRADE_AGGREGATE_SUM) {
$mform->addElement('advcheckbox', 'excluded', get_string('excluded', 'grades'), '<small>('.get_string('warningexcludedsum', 'grades').')</small>');
} else {
$mform->addElement('advcheckbox', 'excluded', get_string('excluded', 'grades'));
}
$mform->addElement('advcheckbox', 'excluded', get_string('excluded', 'grades'));
$mform->addHelpButton('excluded', 'excluded', 'grades');
/// hiding

View File

@ -32,12 +32,8 @@ $action = optional_param('action', 0, PARAM_ALPHA);
$eid = optional_param('eid', 0, PARAM_ALPHANUM);
$category = optional_param('category', null, PARAM_INT);
$aggregationtype = optional_param('aggregationtype', null, PARAM_INT);
$showadvanced = optional_param('showadvanced', -1, PARAM_BOOL); // sticky editing mode
$url = new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid));
if($showadvanced!=-1) {
$url->param("showadvanced",$showadvanced);
}
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
@ -57,41 +53,6 @@ $PAGE->requires->js('/grade/edit/tree/functions.js');
$gpr = new grade_plugin_return(array('type'=>'edit', 'plugin'=>'tree', 'courseid'=>$courseid));
$returnurl = $gpr->get_return_url(null);
/// Build editing on/off buttons
if (!isset($USER->gradeediting)) {
$USER->gradeediting = array();
}
$current_view = '';
if (has_capability('moodle/grade:manage', $context)) {
if (!isset($USER->gradeediting[$course->id])) {
$USER->gradeediting[$course->id] = 0;
}
if ($showadvanced == 1) {
$USER->gradeediting[$course->id] = 1;
} else if ($showadvanced == 0) {
$USER->gradeediting[$course->id] = 0;
}
// page params for the turn editing on
$options = $gpr->get_options();
$options['sesskey'] = sesskey();
if ($USER->gradeediting[$course->id]) {
$options['showadvanced'] = 0;
$current_view = 'fullview';
} else {
$options['showadvanced'] = 1;
$current_view = 'simpleview';
}
} else {
$USER->gradeediting[$course->id] = 0;
$buttons = '';
}
// Change category aggregation if requested
if (!is_null($category) && !is_null($aggregationtype) && confirm_sesskey()) {
if (!$grade_category = grade_category::fetch(array('id'=>$category, 'courseid'=>$courseid))) {
@ -107,8 +68,18 @@ if (!is_null($category) && !is_null($aggregationtype) && confirm_sesskey()) {
}
//first make sure we have proper final grades - we need it for locking changes
$normalisationmessage = null;
$originalweights = grade_helper::fetch_all_natural_weights_for_course($courseid);
grade_regrade_final_grades($courseid);
$alteredweights = grade_helper::fetch_all_natural_weights_for_course($courseid);
if (array_diff($originalweights, $alteredweights)) {
$normalisationmessage = get_string('weightsadjusted', 'grades');
}
// get the grading tree object
// note: total must be first for moving to work correctly, if you want it last moving code must be rewritten!
$gtree = new grade_tree($courseid, false, false);
@ -128,8 +99,6 @@ $switch = grade_get_setting($course->id, 'aggregationposition', $CFG->grade_aggr
$strgrades = get_string('grades');
$strgraderreport = get_string('graderreport', 'grades');
$strcategoriesedit = get_string('categoriesedit', 'grades');
$strcategoriesanditems = get_string('categoriesanditems', 'grades');
$moving = false;
$movingeid = false;
@ -206,21 +175,6 @@ switch ($action) {
break;
}
// Hide advanced columns if moving
if ($grade_edit_tree->moving) {
$original_gradeediting = $USER->gradeediting[$course->id];
$USER->gradeediting[$course->id] = 0;
}
$current_view_str = '';
if ($current_view != '') {
if ($current_view == 'simpleview') {
$current_view_str = get_string('simpleview', 'grades');
} elseif ($current_view == 'fullview') {
$current_view_str = get_string('fullview', 'grades');
}
}
//if we go straight to the db to update an element we need to recreate the tree as
// $grade_edit_tree has already been constructed.
//Ideally we could do the updates through $grade_edit_tree to avoid recreating it
@ -232,7 +186,7 @@ if ($data = data_submitted() and confirm_sesskey()) {
$elements = array();
foreach ($data as $key => $value) {
if (preg_match('/select_(i[0-9]*)/', $key, $matches)) {
if (preg_match('/select_(ig[0-9]*)/', $key, $matches)) {
$elements[] = $matches[1];
}
}
@ -240,77 +194,57 @@ if ($data = data_submitted() and confirm_sesskey()) {
$grade_edit_tree->move_elements($elements, $returnurl);
}
// Category and item field updates
// Update weights (extra credits) on categories and items.
foreach ($data as $key => $value) {
// Grade category text inputs
if (preg_match('/^(aggregation|droplow|keephigh)_([0-9]+)$/', $key, $matches)) {
$param = $matches[1];
$aid = $matches[2];
// Do not allow negative values
$value = clean_param($value, PARAM_INT);
$value = ($value < 0) ? 0 : $value;
$grade_category = grade_category::fetch(array('id'=>$aid, 'courseid'=>$courseid));
$grade_category->$param = $value;
$grade_category->update();
grade_regrade_final_grades($courseid);
$recreatetree = true;
// Grade item text inputs
} elseif (preg_match('/^(grademax|aggregationcoef|multfactor|plusfactor)_([0-9]+)$/', $key, $matches)) {
$param = $matches[1];
$aid = $matches[2];
if (preg_match('/^weight_([0-9]+)$/', $key, $matches)) {
$aid = $matches[1];
$value = unformat_float($value);
$value = clean_param($value, PARAM_FLOAT);
$grade_item = grade_item::fetch(array('id'=>$aid, 'courseid'=>$courseid));
$grade_item = grade_item::fetch(array('id' => $aid, 'courseid' => $courseid));
if ($param === 'grademax' and $value < $grade_item->grademin) {
// better not allow values lower than grade min
$value = $grade_item->grademin;
// Convert weight to aggregation coef2.
$aggcoef = $grade_item->get_coefstring();
if ($aggcoef == 'aggregationcoefextraweightsum') {
// The field 'weight' should only be sent when the checkbox 'weighoverride' is checked,
// so there is not need to set weightoverride here, it is done below.
$value = $value / 100.0;
$grade_item->aggregationcoef2 = $value;
} else if ($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoefextraweight') {
$grade_item->aggregationcoef = $value;
}
$grade_item->update();
$recreatetree = true;
// Grade item checkbox inputs.
} elseif (preg_match('/^(weightoverride)_([0-9]+)$/', $key, $matches)) {
$param = $matches[1];
$aid = $matches[2];
$value = clean_param($value, PARAM_BOOL);
$grade_item = grade_item::fetch(array('id' => $aid, 'courseid' => $courseid));
$grade_item->$param = $value;
$grade_item->update();
grade_regrade_final_grades($courseid);
$recreatetree = true;
// Grade item checkbox inputs
} elseif (preg_match('/^extracredit_([0-9]+)$/', $key, $matches)) { // Sum extra credit checkbox
$aid = $matches[1];
$value = clean_param($value, PARAM_BOOL);
$grade_item = grade_item::fetch(array('id'=>$aid, 'courseid'=>$courseid));
$grade_item->aggregationcoef = $value;
$grade_item->update();
grade_regrade_final_grades($courseid);
$recreatetree = true;
// Grade category checkbox inputs
} elseif (preg_match('/^aggregate(onlygraded|subcats|outcomes)_([0-9]+)$/', $key, $matches)) {
$param = 'aggregate'.$matches[1];
$aid = $matches[2];
$value = clean_param($value, PARAM_BOOL);
$grade_category = grade_category::fetch(array('id'=>$aid, 'courseid'=>$courseid));
$grade_category->$param = $value;
$grade_category->update();
grade_regrade_final_grades($courseid);
$recreatetree = true;
}
}
$originalweights = grade_helper::fetch_all_natural_weights_for_course($courseid);
grade_regrade_final_grades($courseid);
$alteredweights = grade_helper::fetch_all_natural_weights_for_course($courseid);
if (array_diff($originalweights, $alteredweights)) {
$normalisationmessage = get_string('weightsadjusted', 'grades');
}
}
print_grade_page_head($courseid, 'edittree', $current_view, get_string('categoriesedit', 'grades') . ': ' . $current_view_str);
print_grade_page_head($courseid, 'settings', 'setup', get_string('setupgradeslayout', 'grades'));
// Print Table of categories and items
echo $OUTPUT->box_start('gradetreebox generalbox');
@ -323,6 +257,10 @@ echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
if ($recreatetree) {
$grade_edit_tree = new grade_edit_tree($gtree, $movingeid, $gpr);
}
// Check to see if we have a normalisation message to send.
if (!empty($normalisationmessage)) {
echo $OUTPUT->notification($normalisationmessage, 'notifymessage');
}
echo html_writer::table($grade_edit_tree->table);
@ -377,11 +315,6 @@ $PAGE->requires->yui_module('moodle-core-formchangechecker',
$PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
echo $OUTPUT->footer();
// Restore original show/hide preference if moving
if ($moving) {
$USER->gradeediting[$course->id] = $original_gradeediting;
}
die;

View File

@ -37,6 +37,8 @@ if ($id !== 0) {
}
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/grade/edit/tree/index.php',
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
print_error('nocourseid');
@ -97,6 +99,9 @@ if ($parent_category->aggregation == GRADE_AGGREGATE_SUM or $parent_category->ag
} else {
$item->aggregationcoef = format_float($item->aggregationcoef, 4);
}
if ($parent_category->aggregation == GRADE_AGGREGATE_SUM) {
$item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0);
}
$item->cancontrolvisibility = $grade_item->can_control_visibility();
$mform = new edit_item_form(null, array('current'=>$item, 'gpr'=>$gpr));
@ -132,12 +137,15 @@ if ($mform->is_cancelled()) {
unset($data->locked);
unset($data->locktime);
$convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef');
$convert = array('grademax', 'grademin', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef', 'aggregationcoef2');
foreach ($convert as $param) {
if (property_exists($data, $param)) {
$data->$param = unformat_float($data->$param);
}
}
if (isset($data->aggregationcoef2) && $parent_category->aggregation == GRADE_AGGREGATE_SUM) {
$data->aggregationcoef2 = $data->aggregationcoef2 / 100.0;
}
$grade_item = new grade_item(array('id'=>$id, 'courseid'=>$courseid));
grade_item::set_properties($grade_item, $data);
@ -174,10 +182,8 @@ if ($mform->is_cancelled()) {
redirect($returnurl);
}
$return = false;
$buttons = false;
$shownavigation = false;
print_grade_page_head($courseid, 'edittree', null, $heading, $return, $buttons, $shownavigation);
$PAGE->navbar->add($heading);
print_grade_page_head($courseid, 'settings', null, $heading, false, false, false);
$mform->display();

View File

@ -97,6 +97,14 @@ class edit_item_form extends moodleform {
$mform->setType('grademin', PARAM_RAW);
}
$mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades'));
$mform->addHelpButton('weightoverride', 'weightoverride', 'grades');
$mform->addElement('text', 'aggregationcoef2', get_string('weight', 'grades'));
$mform->addHelpButton('aggregationcoef2', 'weight', 'grades');
$mform->setType('aggregationcoef2', PARAM_RAW);
$mform->disabledIf('aggregationcoef2', 'weightoverride');
$mform->addElement('text', 'gradepass', get_string('gradepass', 'grades'));
$mform->addHelpButton('gradepass', 'gradepass', 'grades');
$mform->disabledIf('gradepass', 'gradetype', 'eq', GRADE_TYPE_NONE);
@ -278,8 +286,9 @@ class edit_item_form extends moodleform {
$coefstring = $grade_item->get_coefstring();
if ($coefstring !== '') {
if ($coefstring == 'aggregationcoefextrasum') {
if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') {
// advcheckbox is not compatible with disabledIf!
$coefstring = 'aggregationcoefextrasum';
$element =& $mform->createElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades'));
} else {
$element =& $mform->createElement('text', 'aggregationcoef', get_string($coefstring, 'grades'));
@ -295,6 +304,16 @@ class edit_item_form extends moodleform {
$mform->disabledIf('aggregationcoef', 'parentcategory', 'eq', $parent_category->id);
}
// Remove fields used by natural weighting if the parent category is not using natural weighting.
if ($parent_category->aggregation != GRADE_AGGREGATE_SUM) {
if ($mform->elementExists('weightoverride')) {
$mform->removeElement('weightoverride');
}
if ($mform->elementExists('aggregationcoef2')) {
$mform->removeElement('aggregationcoef2');
}
}
if ($category = $grade_item->get_item_category()) {
if ($category->aggregation == GRADE_AGGREGATE_SUM) {
if ($mform->elementExists('gradetype')) {

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,9 @@ if ($id !== 0) {
$url->param('id', $id);
}
$PAGE->set_url($url);
$PAGE->set_pagelayout('admin');
navigation_node::override_active_url(new moodle_url('/grade/edit/tree/index.php',
array('id'=>$courseid)));
if (!$course = $DB->get_record('course', array('id' => $courseid))) {
print_error('nocourseid');
@ -214,7 +217,8 @@ if ($data = $mform->get_data()) {
redirect($returnurl);
}
print_grade_page_head($courseid, 'edittree', null, $heading);
$PAGE->navbar->add($heading);
print_grade_page_head($courseid, 'settings', null, $heading, false, false, false);
if (!grade_outcome::fetch_all_available($COURSE->id)) {
echo $OUTPUT->confirm(get_string('nooutcomes', 'grades'), $CFG->wwwroot.'/grade/edit/outcome/course.php?id='.$courseid, $returnurl);

View File

@ -458,6 +458,48 @@ function grade_get_graded_users_select($report, $course, $userid, $groupid, $inc
return $select;
}
/**
* Hide warning about changed grades during upgrade to 2.8.
*
* @param int $courseid The current course id.
*/
function hide_natural_aggregation_upgrade_notice($courseid) {
set_config('show_sumofgrades_upgrade_' . $courseid, false);
}
/**
* Print warning about changed grades during upgrade to 2.8.
*
* @param int $courseid The current course id.
* @param context $context The course context.
* @param boolean $return return as string
*
* @return nothing or string if $return true
*/
function print_natural_aggregation_upgrade_notice($courseid, $context, $return=false) {
global $OUTPUT;
$html = '';
$show = get_config('core', 'show_sumofgrades_upgrade_' . $courseid);
if ($show) {
$message = get_string('sumofgradesupgradedgrades', 'grades');
$hidemessage = get_string('sumofgradesupgradedgradeshidemessage', 'grades');
$urlparams = array( 'id' => $courseid,
'seensumofgradesupgradedgrades' => true,
'sesskey' => sesskey());
$goawayurl = new moodle_url('/grade/report/grader/index.php', $urlparams);
$goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get');
$html .= $OUTPUT->notification($message, 'notifysuccess');
$html .= $goawaybutton;
}
if ($return) {
return $html;
} else {
echo $html;
}
}
/**
* Print grading plugin selection popup form.
*
@ -609,13 +651,6 @@ function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
$plugin_info['report'] = $reports;
}
//showing grade categories and items make no sense if we're not within a course
if ($courseid!=$SITE->id) {
if ($edittree = grade_helper::get_info_edit_structure($courseid)) {
$plugin_info['edittree'] = $edittree;
}
}
if ($scale = grade_helper::get_info_scales($courseid)) {
$plugin_info['scale'] = array('view'=>$scale);
}
@ -650,11 +685,9 @@ function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
}
}
//hide course settings if we're not in a course
if ($courseid!=$SITE->id) {
if ($setting = grade_helper::get_info_manage_settings($courseid)) {
$plugin_info['settings'] = array('course'=>$setting);
}
// Hide course settings if we're not in a course
if ($settings = grade_helper::get_info_manage_settings($courseid)) {
$plugin_info['settings'] = $settings;
}
// Put preferences last
@ -778,7 +811,9 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
$buttons = $OUTPUT->render($buttons);
}
$PAGE->set_button($buttons);
grade_extend_settings($plugin_info, $courseid);
if ($courseid != SITEID) {
grade_extend_settings($plugin_info, $courseid);
}
$returnval = $OUTPUT->header();
if (!$return) {
@ -791,7 +826,8 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
}
if ($shownavigation) {
if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
if ($courseid != SITEID &&
($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN)) {
$returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
}
@ -801,7 +837,8 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
echo $OUTPUT->heading($heading);
}
if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
if ($courseid != SITEID &&
($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS)) {
$returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
}
}
@ -1171,19 +1208,22 @@ class grade_structure {
} else if (($is_course or $is_category) and ($is_scale or $is_value)) {
if ($category = $element['object']->get_item_category()) {
$aggrstrings = grade_helper::get_aggregation_strings();
$stragg = $aggrstrings[$category->aggregation];
switch ($category->aggregation) {
case GRADE_AGGREGATE_MEAN:
case GRADE_AGGREGATE_MEDIAN:
case GRADE_AGGREGATE_WEIGHTED_MEAN:
case GRADE_AGGREGATE_WEIGHTED_MEAN2:
case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
$stragg = get_string('aggregation', 'grades');
return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
case GRADE_AGGREGATE_SUM:
$stragg = get_string('aggregation', 'grades');
return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
default:
return '<img src="'.$OUTPUT->pix_url('i/calc') . '" ' .
'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
}
}
@ -1236,10 +1276,11 @@ class grade_structure {
* @param bool $withlink Whether or not this header has a link
* @param bool $icon Whether or not to display an icon with this header
* @param bool $spacerifnone return spacer if no icon found
* @param bool $withdescription Show description if defined by this item.
*
* @return string header
*/
public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false, $withdescription=false) {
$header = '';
if ($icon) {
@ -1264,6 +1305,13 @@ class grade_structure {
}
}
if ($withdescription) {
$desc = $element['object']->get_description();
if (!empty($desc)) {
$header .= '<div class="gradeitemdescription">' . s($desc) . '</div><div class="gradeitemdescriptionfiller"></div>';
}
}
return $header;
}
@ -1416,7 +1464,7 @@ class grade_structure {
* @return string eid
*/
public function get_item_eid($grade_item) {
return 'i'.$grade_item->id;
return 'ig'.$grade_item->id;
}
/**
@ -1454,6 +1502,34 @@ class grade_structure {
return $strparams;
}
/**
* Return a reset icon for the given element.
*
* @param array $element An array representing an element in the grade_tree
* @param object $gpr A grade_plugin_return object
* @return string
*/
public function get_reset_icon($element, $gpr) {
global $CFG, $OUTPUT;
// Limit to category items set to use the natural weights aggregation method, and users
// with the capability to manage grades.
if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM ||
!has_capability('moodle/grade:manage', $this->context)) {
return '';
}
$str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element));
$url = new moodle_url('/grade/edit/tree/action.php', array(
'id' => $this->courseid,
'action' => 'resetweights',
'eid' => $element['eid'],
'sesskey' => sesskey(),
));
return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str));
}
/**
* Return edit icon for give element
*
@ -1779,7 +1855,7 @@ class grade_seq extends grade_structure {
$userid = $matches[2];
//extra security check - the grade item must be in this tree
if (!$item_el = $this->locate_element('i'.$itemid)) {
if (!$item_el = $this->locate_element('ig'.$itemid)) {
return null;
}
@ -1795,7 +1871,7 @@ class grade_seq extends grade_structure {
return null;
}
//extra security check - the grade item must be in this tree
if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
if (!$item_el = $this->locate_element('ig'.$grade->itemid)) {
return null;
}
$grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
@ -1993,9 +2069,9 @@ class grade_tree extends grade_structure {
// prepare unique identifier
if ($element['type'] == 'category') {
$element['eid'] = 'c'.$element['object']->id;
$element['eid'] = 'cg'.$element['object']->id;
} else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
$element['eid'] = 'i'.$element['object']->id;
$element['eid'] = 'ig'.$element['object']->id;
$this->items[$element['object']->id] =& $element['object'];
}
@ -2099,7 +2175,7 @@ class grade_tree extends grade_structure {
$userid = $matches[2];
//extra security check - the grade item must be in this tree
if (!$item_el = $this->locate_element('i'.$itemid)) {
if (!$item_el = $this->locate_element('ig'.$itemid)) {
return null;
}
@ -2115,7 +2191,7 @@ class grade_tree extends grade_structure {
return null;
}
//extra security check - the grade item must be in this tree
if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
if (!$item_el = $this->locate_element('ig'.$grade->itemid)) {
return null;
}
$grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
@ -2343,8 +2419,11 @@ function grade_extend_settings($plugininfo, $courseid) {
}
}
if ($setting = grade_helper::get_info_manage_settings($courseid)) {
$gradenode->add(get_string('coursegradesettings', 'grades'), $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
if ($settings = grade_helper::get_info_manage_settings($courseid)) {
$settingsnode = $gradenode->add($strings['settings'], null, navigation_node::TYPE_CONTAINER);
foreach ($settings as $setting) {
$settingsnode->add($setting->string, $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
}
}
if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
@ -2368,13 +2447,6 @@ function grade_extend_settings($plugininfo, $courseid) {
$gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', ''));
}
if ($categories = grade_helper::get_info_edit_structure($courseid)) {
$categoriesnode = $gradenode->add(get_string('categoriesanditems','grades'), null, navigation_node::TYPE_CONTAINER);
foreach ($categories as $category) {
$categoriesnode->add($category->string, $category->link, navigation_node::TYPE_SETTING, null, $category->id, new pix_icon('i/report', ''));
}
}
if ($gradenode->contains_active_node()) {
// If the gradenode is active include the settings base node (gradeadministration) in
// the navbar, typcially this is ignored.
@ -2424,11 +2496,6 @@ abstract class grade_helper {
* @var grade_plugin_info|false
*/
protected static $outcomeinfo = null;
/**
* Cached info on edit structure {@see get_info_edit_structure}
* @var array|false
*/
protected static $edittree = null;
/**
* Cached leftter info {@see get_info_letters}
* @var grade_plugin_info|false
@ -2449,12 +2516,16 @@ abstract class grade_helper {
* @var array
*/
protected static $pluginstrings = null;
/**
* Cached grade aggregation strings
* @var array
*/
protected static $aggregationstrings = null;
/**
* Gets strings commonly used by the describe plugins
*
* report => get_string('view'),
* edittree => get_string('edittree', 'grades'),
* scale => get_string('scales'),
* outcome => get_string('outcomes', 'grades'),
* letter => get_string('letters', 'grades'),
@ -2469,7 +2540,6 @@ abstract class grade_helper {
if (self::$pluginstrings === null) {
self::$pluginstrings = array(
'report' => get_string('view'),
'edittree' => get_string('edittree', 'grades'),
'scale' => get_string('scales'),
'outcome' => get_string('outcomes', 'grades'),
'letter' => get_string('letters', 'grades'),
@ -2481,21 +2551,48 @@ abstract class grade_helper {
}
return self::$pluginstrings;
}
/**
* Gets strings describing the available aggregation methods.
*
* @return array
*/
public static function get_aggregation_strings() {
if (self::$aggregationstrings === null) {
self::$aggregationstrings = array(
GRADE_AGGREGATE_MEAN => get_string('aggregatemean', 'grades'),
GRADE_AGGREGATE_WEIGHTED_MEAN => get_string('aggregateweightedmean', 'grades'),
GRADE_AGGREGATE_WEIGHTED_MEAN2 => get_string('aggregateweightedmean2', 'grades'),
GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'),
GRADE_AGGREGATE_MEDIAN => get_string('aggregatemedian', 'grades'),
GRADE_AGGREGATE_MIN => get_string('aggregatemin', 'grades'),
GRADE_AGGREGATE_MAX => get_string('aggregatemax', 'grades'),
GRADE_AGGREGATE_MODE => get_string('aggregatemode', 'grades'),
GRADE_AGGREGATE_SUM => get_string('aggregatesum', 'grades')
);
}
return self::$aggregationstrings;
}
/**
* Get grade_plugin_info object for managing settings if the user can
*
* @param int $courseid
* @return grade_plugin_info
* @return grade_plugin_info[]
*/
public static function get_info_manage_settings($courseid) {
if (self::$managesetting !== null) {
return self::$managesetting;
}
$context = context_course::instance($courseid);
if (has_capability('moodle/grade:manage', $context)) {
self::$managesetting = new grade_plugin_info('coursesettings', new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), get_string('course'));
} else {
self::$managesetting = false;
self::$managesetting = array();
if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) {
self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings',
new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)),
get_string('coursegradesettings', 'grades'));
self::$managesetting['setup'] = new grade_plugin_info('setup',
new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)),
get_string('setupgradeslayout', 'grades'));
}
return self::$managesetting;
}
@ -2616,26 +2713,6 @@ abstract class grade_helper {
}
return self::$outcomeinfo;
}
/**
* Get information on editing structures
* @param int $courseid
* @return array
*/
public static function get_info_edit_structure($courseid) {
if (self::$edittree !== null) {
return self::$edittree;
}
if (has_capability('moodle/grade:manage', context_course::instance($courseid))) {
$url = new moodle_url('/grade/edit/tree/index.php', array('sesskey'=>sesskey(), 'showadvanced'=>'0', 'id'=>$courseid));
self::$edittree = array(
'simpleview' => new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades')),
'fullview' => new grade_plugin_info('fullview', new moodle_url($url, array('showadvanced'=>'1')), get_string('fullview', 'grades'))
);
} else {
self::$edittree = false;
}
return self::$edittree;
}
/**
* Get information on letters
* @param int $courseid
@ -2836,5 +2913,22 @@ abstract class grade_helper {
return $fields;
}
/**
* This helper method gets a snapshot of all the weights for a course.
* It is used as a quick method to see if any wieghts have been automatically adjusted.
* @param int $courseid
* @return array of itemid -> aggregationcoef2
*/
public static function fetch_all_natural_weights_for_course($courseid) {
global $DB;
$result = array();
$records = $DB->get_records('grade_items', array('courseid'=>$courseid), 'id', 'id, aggregationcoef2');
foreach ($records as $record) {
$result[$record->id] = $record->aggregationcoef2;
}
return $result;
}
}

View File

@ -128,6 +128,13 @@ $reportname = get_string('pluginname', 'gradereport_grader');
// Print header
print_grade_page_head($COURSE->id, 'report', 'grader', $reportname, false, $buttons);
// Hide the following warning if the user told it to go away.
if (optional_param('seensumofgradesupgradedgrades', false, PARAM_BOOL) && confirm_sesskey()) {
hide_natural_aggregation_upgrade_notice($courseid);
}
// This shows a notice about the upgrade to Natural aggregation.
print_natural_aggregation_upgrade_notice($COURSE->id, $context);
//Initialise the grader report object that produces the table
//the class grade_report_grader_ajax was removed as part of MDL-21562
$report = new grade_report_grader($courseid, $gpr, $context, $page, $sortitemid);

View File

@ -1049,6 +1049,12 @@ class grade_report_grader extends grade_report {
if ($item->needsupdate) {
$itemcell->text .= "<span class='gradingerror{$hidden}{$gradepass}'>" . $error . "</span>";
} else {
// The max and min for an aggregation may be different to the grade_item.
if (!is_null($gradeval)) {
$item->grademax = $grade->rawgrademax;
$item->grademin = $grade->rawgrademin;
}
$itemcell->text .= "<span class='gradevalue{$hidden}{$gradepass}'>" .
grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null) . "</span>";
if ($showanalysisicon) {
@ -1690,11 +1696,11 @@ class grade_report_grader extends grade_report {
public static function do_process_action($target, $action, $courseid = null) {
global $DB;
// TODO: this code should be in some grade_tree static method
$targettype = substr($target, 0, 1);
$targetid = substr($target, 1);
$targettype = substr($target, 0, 2);
$targetid = substr($target, 2);
// TODO: end
if ($targettype !== 'c') {
if ($targettype !== 'cg') {
// The following code only works with categories.
return true;
}

View File

@ -414,7 +414,7 @@ abstract class grade_report {
* @param string $courseid the course id
* @param string $course_item an instance of grade_item
* @param string $finalgrade the grade for the course_item
* @return array[] containing values for 'grade', 'grademax' and 'grademin'
* @return array[] containing values for 'grade', 'grademax', 'grademin', 'aggregationstatus' and 'aggregationweight'
*/
protected function blank_hidden_total_and_adjust_bounds($courseid, $course_item, $finalgrade) {
global $CFG, $DB;
@ -426,8 +426,18 @@ abstract class grade_report {
// If we're dealing with multiple courses we need to know when we've moved on to a new course.
static $previous_courseid = null;
$coursegradegrade = grade_grade::fetch(array('userid'=>$this->user->id, 'itemid'=>$course_item->id));
$grademin = $course_item->grademin;
$grademax = $course_item->grademax;
if ($coursegradegrade) {
$grademin = $coursegradegrade->rawgrademin;
$grademax = $coursegradegrade->rawgrademax;
} else {
$coursegradegrade = new grade_grade(array('userid'=>$this->user->id, 'itemid'=>$course_item->id), false);
}
$hint = $coursegradegrade->get_aggregation_hint();
$aggregationstatus = $hint['status'];
$aggregationweight = $hint['weight'];
if (!is_array($this->showtotalsifcontainhidden)) {
debugging('showtotalsifcontainhidden should be an array', DEBUG_DEVELOPER);
@ -435,7 +445,11 @@ abstract class grade_report {
}
if ($this->showtotalsifcontainhidden[$courseid] == GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN) {
return array('grade' => $finalgrade, 'grademin' => $grademin, 'grademax' => $grademax);
return array('grade' => $finalgrade,
'grademin' => $grademin,
'grademax' => $grademax,
'aggregationstatus' => $aggregationstatus,
'aggregationweight' => $aggregationweight);
}
// If we've moved on to another course or user, reload the grades.
@ -471,19 +485,31 @@ abstract class grade_report {
}
//if the item definitely depends on a hidden item
if (array_key_exists($course_item->id, $hiding_affected['altered'])) {
if (array_key_exists($course_item->id, $hiding_affected['altered']) ||
array_key_exists($course_item->id, $hiding_affected['alteredgrademin']) ||
array_key_exists($course_item->id, $hiding_affected['alteredgrademax']) ||
array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus']) ||
array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) {
if (!$this->showtotalsifcontainhidden[$courseid]) {
//hide the grade
$finalgrade = null;
} else {
//use reprocessed marks that exclude hidden items
$finalgrade = $hiding_affected['altered'][$course_item->id];
if (!empty($hiding_affected['alteredgrademin'][$course_item->id])) {
if (array_key_exists($course_item->id, $hiding_affected['altered'])) {
$finalgrade = $hiding_affected['altered'][$course_item->id];
}
if (array_key_exists($course_item->id, $hiding_affected['alteredgrademin'])) {
$grademin = $hiding_affected['alteredgrademin'][$course_item->id];
}
if (!empty($hiding_affected['alteredgrademax'][$course_item->id])) {
if (array_key_exists($course_item->id, $hiding_affected['alteredgrademax'])) {
$grademax = $hiding_affected['alteredgrademax'][$course_item->id];
}
if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus'])) {
$aggregationstatus = $hiding_affected['alteredaggregationstatus'][$course_item->id];
}
if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) {
$aggregationweight = $hiding_affected['alteredaggregationweight'][$course_item->id];
}
}
} else if (!empty($hiding_affected['unknown'][$course_item->id])) {
//not sure whether or not this item depends on a hidden item
@ -494,16 +520,22 @@ abstract class grade_report {
//use reprocessed marks that exclude hidden items
$finalgrade = $hiding_affected['unknown'][$course_item->id];
if (!empty($hiding_affected['alteredgrademin'][$course_item->id])) {
if (array_key_exists($course_item->id, $hiding_affected['alteredgrademin'])) {
$grademin = $hiding_affected['alteredgrademin'][$course_item->id];
}
if (!empty($hiding_affected['alteredgrademax'][$course_item->id])) {
if (array_key_exists($course_item->id, $hiding_affected['alteredgrademax'])) {
$grademax = $hiding_affected['alteredgrademax'][$course_item->id];
}
if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus'])) {
$aggregationstatus = $hiding_affected['alteredaggregationstatus'][$course_item->id];
}
if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) {
$aggregationweight = $hiding_affected['alteredaggregationweight'][$course_item->id];
}
}
}
return array('grade' => $finalgrade, 'grademin' => $grademin, 'grademax' => $grademax);
return array('grade' => $finalgrade, 'grademin' => $grademin, 'grademax' => $grademax, 'aggregationstatus'=>$aggregationstatus, 'aggregationweight'=>$aggregationweight);
}
/**

View File

@ -179,6 +179,13 @@ class grade_report_overview extends grade_report {
$course_item->grademax = $adjustedgrade['grademax'];
$course_item->grademin = $adjustedgrade['grademin'];
}
} else {
// We must use the rawgrademin / rawgrademax because it can be different for
// each grade_grade when items are excluded from sum of grades.
if (!is_null($finalgrade)) {
$course_item->grademin = $course_grade->rawgrademin;
$course_item->grademax = $course_grade->rawgrademax;
}
}
$data = array($courselink, grade_format_gradevalue($finalgrade, $course_item, true));

View File

@ -129,6 +129,12 @@ class grade_report_user extends grade_report {
*/
public $showlettergrade = false;
/**
* Show the calculated contribution to the course total column.
* @var bool
*/
public $showcontributiontocoursetotal = false;
/**
* Show average grades in the report, default false.
* @var false
@ -170,6 +176,15 @@ class grade_report_user extends grade_report {
*/
protected $viewasuser = false;
/**
* An array that collects the aggregationhints for every
* grade_item. The hints contain grade, grademin, grademax
* status, weight and parent.
*
* @var array
*/
protected $aggregationhints = array();
/**
* Constructor. Sets local copies of user preferences and initialises grade_tree.
* @param int $courseid
@ -191,6 +206,7 @@ class grade_report_user extends grade_report {
$this->showrange = grade_get_setting($this->courseid, 'report_user_showrange', !empty($CFG->grade_report_user_showrange));
$this->showfeedback = grade_get_setting($this->courseid, 'report_user_showfeedback', !empty($CFG->grade_report_user_showfeedback));
$this->showweight = grade_get_setting($this->courseid, 'report_user_showweight', !empty($CFG->grade_report_user_showweight));
$this->showcontributiontocoursetotal = grade_get_setting($this->courseid, 'report_user_showcontributiontocoursetotal', !empty($CFG->grade_report_user_showcontributiontocoursetotal));
$this->showlettergrade = grade_get_setting($this->courseid, 'report_user_showlettergrade', !empty($CFG->grade_report_user_showlettergrade));
$this->showaverage = grade_get_setting($this->courseid, 'report_user_showaverage', !empty($CFG->grade_report_user_showaverage));
@ -327,6 +343,11 @@ class grade_report_user extends grade_report {
$this->tablecolumns[] = 'feedback';
$this->tableheaders[] = $this->get_lang_string('feedback', 'grades');
}
if ($this->showcontributiontocoursetotal) {
$this->tablecolumns[] = 'contributiontocoursetotal';
$this->tableheaders[] = $this->get_lang_string('contributiontocoursetotal', 'grades');
}
}
function fill_table() {
@ -338,6 +359,11 @@ class grade_report_user extends grade_report {
return true;
}
/**
* Fill the table with data.
*
* @param $element - An array containing the table data for the current row.
*/
private function fill_table_recursive(&$element) {
global $DB, $CFG;
@ -346,11 +372,12 @@ class grade_report_user extends grade_report {
$grade_object = $element['object'];
$eid = $grade_object->id;
$element['userid'] = $this->user->id;
$fullname = $this->gtree->get_element_header($element, true, true, true);
$fullname = $this->gtree->get_element_header($element, true, true, true, true);
$data = array();
$hidden = '';
$excluded = '';
$class = '';
$itemlevel = ($type == 'categoryitem' || $type == 'category' || $type == 'courseitem') ? $depth : ($depth + 1);
$class = 'level' . $itemlevel . ' level' . ($itemlevel % 2 ? 'odd' : 'even');
$classfeedback = '';
// If this is a hidden grade category, hide it completely from the user
@ -408,13 +435,15 @@ class grade_report_user extends grade_report {
if (!$hide) {
/// Excluded Item
/**
if ($grade_grade->is_excluded()) {
$fullname .= ' ['.get_string('excluded', 'grades').']';
$excluded = ' excluded';
}
**/
/// Other class information
$class = "$hidden $excluded";
$class .= $hidden . $excluded;
if ($this->switch) { // alter style based on whether aggregation is first or last
$class .= ($type == 'categoryitem' or $type == 'courseitem') ? " ".$alter."d$depth baggt b2b" : " item b1b";
} else {
@ -433,17 +462,27 @@ class grade_report_user extends grade_report {
/// Actual Grade
$gradeval = $grade_grade->finalgrade;
$hint = $grade_grade->get_aggregation_hint();
if (!$this->canviewhidden) {
/// Virtual Grade (may be calculated excluding hidden items etc).
$adjustedgrade = $this->blank_hidden_total_and_adjust_bounds($this->courseid,
$grade_grade->grade_item,
$gradeval);
$gradeval = $adjustedgrade['grade'];
// We temporarily adjust the view of this grade item - because the min and
// max are affected by the hidden values in the aggregation.
$grade_grade->grade_item->grademax = $adjustedgrade['grademax'];
$grade_grade->grade_item->grademin = $adjustedgrade['grademin'];
$hint['status'] = $adjustedgrade['aggregationstatus'];
$hint['weight'] = $adjustedgrade['aggregationweight'];
} else {
// The max and min for an aggregation may be different to the grade_item.
if (!is_null($gradeval)) {
$grade_grade->grade_item->grademax = $grade_grade->rawgrademax;
$grade_grade->grade_item->grademin = $grade_grade->rawgrademin;
}
}
if ($this->showfeedback) {
@ -456,8 +495,13 @@ class grade_report_user extends grade_report {
$data['weight']['content'] = '-';
$data['weight']['headers'] = "$header_cat $header_row weight";
// has a weight assigned, might be extra credit
if ($grade_object->aggregationcoef > 0 && $type <> 'courseitem') {
$data['weight']['content'] = number_format($grade_object->aggregationcoef,2);
// This obliterates the weight because it provides a more informative description.
if (is_numeric($hint['weight'])) {
$data['weight']['content'] = format_float($hint['weight'] * 100.0, 2) . ' %';
}
if ($hint['status'] != 'used' && $hint['status'] != 'unknown') {
$data['weight']['content'] .= '<br>' . get_string('aggregationhint' . $hint['status'], 'grades');
}
}
@ -586,6 +630,22 @@ class grade_report_user extends grade_report {
}
$data['feedback']['headers'] = "$header_cat $header_row feedback";
}
// Contribution to the course total column.
if ($this->showcontributiontocoursetotal) {
$data['contributiontocoursetotal']['class'] = $class;
$data['contributiontocoursetotal']['content'] = '-';
$data['contributiontocoursetotal']['headers'] = "$header_cat $header_row contributiontocoursetotal";
$hint['grademax'] = $grade_grade->grade_item->grademax;
$hint['grademin'] = $grade_grade->grade_item->grademin;
$hint['grade'] = $gradeval;
$parent = $grade_object->load_parent_category();
if ($grade_object->is_category_item()) {
$parent = $parent->load_parent_category();
}
$hint['parent'] = $parent->load_grade_item()->id;
$this->aggregationhints[$grade_grade->itemid] = $hint;
}
}
}
@ -606,6 +666,9 @@ class grade_report_user extends grade_report {
}
/// Add this row to the overall system
foreach ($data as $key => $celldata) {
$data[$key]['class'] .= ' column-' . $key;
}
$this->tabledata[] = $data;
/// Recursively iterate through all child elements
@ -614,6 +677,83 @@ class grade_report_user extends grade_report {
$this->fill_table_recursive($element['children'][$key]);
}
}
// Check we are showing this column, and we are looking at the root of the table.
// This should be the very last thing this fill_table_recursive function does.
if ($this->showcontributiontocoursetotal && ($type == 'category' && $depth == 1)) {
// We should have collected all the hints by now - walk the tree again and build the contributions column.
$this->fill_contributions_column($element);
}
}
/**
* 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.
$graderange = $this->aggregationhints[$itemid]['grademax'];
$gradeval = $this->aggregationhints[$itemid]['grade'] / $graderange;
} else {
$graderange = $this->aggregationhints[$itemid]['grademax'] - $this->aggregationhints[$itemid]['grademin'];
$gradeval = ($this->aggregationhints[$itemid]['grade'] - $this->aggregationhints[$itemid]['grademin']) / $graderange;
}
// Multiply the normalised value by the weight
// of all the categories higher in the tree.
do {
if (!is_null($this->aggregationhints[$itemid]['weight'])) {
$gradeval *= $this->aggregationhints[$itemid]['weight'];
}
// 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.
$gradeval *= $this->aggregationhints[$itemid]['grademax'];
// 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.
$decimals = $grade_object->get_decimals();
$this->tabledata[$key]['contributiontocoursetotal']['content'] = format_float($gradeval, $decimals, true);
break;
}
}
}
}
}
/**
@ -632,10 +772,10 @@ class grade_report_user extends grade_report {
class='boxaligncenter generaltable user-grade'>
<thead>
<tr>
<th id='".$this->tablecolumns[0]."' class=\"header\" colspan='$maxspan'>".$this->tableheaders[0]."</th>\n";
<th id='".$this->tablecolumns[0]."' class=\"header column-{$this->tablecolumns[0]}\" colspan='$maxspan'>".$this->tableheaders[0]."</th>\n";
for ($i = 1; $i < count($this->tableheaders); $i++) {
$html .= "<th id='".$this->tablecolumns[$i]."' class=\"header\">".$this->tableheaders[$i]."</th>\n";
$html .= "<th id='".$this->tablecolumns[$i]."' class=\"header column-{$this->tablecolumns[$i]}\">".$this->tableheaders[$i]."</th>\n";
}
$html .= "
@ -901,6 +1041,14 @@ function grade_report_user_settings_definition(&$mform) {
}
$mform->addElement('select', 'report_user_showlettergrade', get_string('showlettergrade', 'grades'), $options);
if (empty($CFG->grade_report_user_showcontributiontocoursetotal)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
} else {
$options[-1] = get_string('defaultprev', 'grades', $options[$CFG->grade_report_user_showcontributiontocoursetotal]);
}
$mform->addElement('select', 'report_user_showcontributiontocoursetotal', get_string('showcontributiontocoursetotal', 'grades'), $options);
$mform->addHelpButton('report_user_showcontributiontocoursetotal', 'showcontributiontocoursetotal', 'grades');
if (empty($CFG->grade_report_user_showrange)) {
$options[-1] = get_string('defaultprev', 'grades', $options[0]);
@ -944,6 +1092,7 @@ function grade_report_user_settings_definition(&$mform) {
$mform->addElement('select', 'report_user_showtotalsifcontainhidden', get_string('hidetotalifhiddenitems', 'grades'), $options);
$mform->addHelpButton('report_user_showtotalsifcontainhidden', 'hidetotalifhiddenitems', 'grades');
}
/**

View File

@ -47,4 +47,5 @@ if ($ADMIN->fulltree) {
array(GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN => get_string('hide'),
GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowexhiddenitems', 'grades'),
GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN => get_string('hidetotalshowinchiddenitems', 'grades'))));
$settings->add(new admin_setting_configcheckbox('grade_report_user_showcontributiontocoursetotal', get_string('showcontributiontocoursetotal', 'grades'), get_string('showcontributiontocoursetotal_help', 'grades'), 0));
}

View File

@ -57,3 +57,6 @@
table.user-grade td.feedbacktext {text-align:left;width: 40%;font-size: 0.8em;white-space:normal;}
table.user-grade td.itemcenter {text-align:center;}
.user-grade .gradeitemdescription { font-weight: normal; position: absolute; }
.user-grade .gradeitemdescriptionfiller { clear: left; height: 1em; }
.user-grade .gradeitemdescription { font-weight: normal; position: absolute;}

View File

@ -27,7 +27,8 @@
require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
use Behat\Behat\Context\Step\Given as Given;
use Behat\Behat\Context\Step\Given as Given,
Behat\Gherkin\Node\TableNode as TableNode;
class behat_grade extends behat_base {
@ -46,4 +47,26 @@ class behat_grade extends behat_base {
return new Given('I set the field "' . $this->escape($fieldstr) . '" to "' . $grade . '"');
}
/**
* Changes the settings of a grade item or category or the course.
*
* Teacher must be either on the grade setup page or on the Grader report page with editing mode turned on.
*
* @Given /^I set the following settings for grade item "(?P<grade_item_string>(?:[^"]|\\")*)":$/
* @param string $gradeitem
* @param TableNode $data
* @return Given[]
*/
public function i_set_the_following_settings_for_grade_item($gradeitem, TableNode $data) {
$savechanges = get_string('savechanges', 'grades');
$edit = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('edit') . ' ');
$gradeitem = $this->getSession()->getSelectorsHandler()->xpathLiteral($gradeitem);
$linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
return array(
new Given('I click on "' . $this->escape($linkxpath) . '" "xpath_element"'),
new Given('I set the following fields to these values:', $data),
new Given('I press "' . $this->escape($savechanges) . '"'),
);
}
}

View File

@ -18,8 +18,8 @@ Feature: We can use calculated grade totals
| student1 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1|
| Sub category 2 | C1|
| Sub category 1 | C1 |
| Sub category 2 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| assign | C1 | a1 | Test assignment one | Submit something! | 300 |
@ -28,14 +28,18 @@ Feature: We can use calculated grade totals
| assign | C1 | a4 | Test assignment four | Submit nothing! | 150 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | gradecategory | grade |
| assign | C1 | a5 | Test assignment five | Submit something! | Sub category 1 | 200
| assign | C1 | a6 | Test assignment six | Submit something! | Sub category 1 | 100
| assign | C1 | a7 | Test assignment seven | Submit nothing! | Sub category 1 | 150
| assign | C1 | a5 | Test assignment five | Submit something! | Sub category 1 | 20 |
| assign | C1 | a6 | Test assignment six | Submit something! | Sub category 1 | 10 |
| assign | C1 | a7 | Test assignment seven | Submit nothing! | Sub category 1 | 15 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | gradecategory | grade |
| assign | C1 | a8 | Test assignment eight | Submit something! | Sub category 2 | 200
| assign | C1 | a9 | Test assignment nine | Submit something! | Sub category 2 | 100
| assign | C1 | 10 | Test assignment ten | Submit nothing! | Sub category 2 | 150
| assign | C1 | a8 | Test assignment eight | Submit something! | Sub category 2 | 20 |
| assign | C1 | a9 | Test assignment nine | Submit something! | Sub category 2 | 10 |
| assign | C1 | 10 | Test assignment ten | Submit nothing! | Sub category 2 | 15 |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Grades"
@ -48,35 +52,28 @@ Feature: We can use calculated grade totals
And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment eight"
And I give the grade "5.00" to the user "Student 1" for the grade item "Test assignment nine"
And I press "Save changes"
And I click on "Edit assign Test assignment two" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment five" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment eight" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I follow "Course grade settings"
And I set the following settings for grade item "Test assignment two":
| Hidden | 1 |
And I set the following settings for grade item "Test assignment five":
| Hidden | 1 |
And I set the following settings for grade item "Test assignment eight":
| Hidden | 1 |
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Grade display type" to "Real (percentage)"
And I press "Save changes"
@javascript
Scenario: Mean of grades aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Mean of grades"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Mean of grades"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Mean of grades"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Mean of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Mean of grades |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Mean of grades |
| Exclude empty grades | 0 |
And I turn editing mode off
Then I should see "30.00 (30.00 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -88,27 +85,20 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Weighted mean of grades aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Weighted mean of grades"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Weighted mean of grades"
And I expand all fieldsets
And I set the field "Item weight" to "1"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Weighted mean of grades"
And I expand all fieldsets
And I set the field "Item weight" to "1"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment one" "link"
And I expand all fieldsets
And I set the field "Item weight" to "3"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Weighted mean of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Weighted mean of grades |
| Item weight | 1 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Weighted mean of grades |
| Item weight | 1 |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment one":
| Item weight | 3 |
And I turn editing mode off
Then I should see "27.14 (27.14 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -120,24 +110,18 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Simple weighted mean of grades aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Simple weighted mean of grades"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Simple weighted mean of grades"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Simple weighted mean of grades"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment one" "link"
And I expand all fieldsets
And I click on "Extra credit" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Simple weighted mean of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Simple weighted mean of grades |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Simple weighted mean of grades |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment one":
| Extra credit | 1 |
And I turn editing mode off
Then I should see "45.19 (45.19 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -149,24 +133,18 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Mean of grades (with extra credits) aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Mean of grades (with extra credits)"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Mean of grades (with extra credits)"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Mean of grades (with extra credits)"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment one" "link"
And I expand all fieldsets
And I set the field "Extra credit weight" to "2"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Mean of grades (with extra credits) |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Mean of grades (with extra credits) |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Mean of grades (with extra credits) |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment one":
| Extra credit weight | 2 |
And I turn editing mode off
Then I should see "42.50 (42.50 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -178,20 +156,16 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Median of grades aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Median of grades"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Median of grades"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Median of grades"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Median of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Median of grades |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Median of grades |
| Exclude empty grades | 0 |
And I turn editing mode off
Then I should see "26.67 (26.67 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -203,26 +177,20 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Lowest grade aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Lowest grade"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Lowest grade"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Lowest grade"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment five" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment four" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Lowest grade |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Lowest grade |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Lowest grade |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment five":
| Hidden | 1 |
And I set the following settings for grade item "Test assignment four":
| Hidden | 1 |
And I turn editing mode off
Then I should see "0.00 (0.00 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -234,23 +202,18 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Highest grade aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Highest grade"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Highest grade"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Highest grade"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment one" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Highest grade |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Highest grade |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Highest grade |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment one":
| Hidden | 1 |
And I turn editing mode off
Then I should see "50.00 (50.00 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -262,23 +225,18 @@ Feature: We can use calculated grade totals
@javascript
Scenario: Mode of grades aggregation
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Mode of grades"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Mode of grades"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Mode of grades"
And I click on "Show more..." "link"
And I click on "Exclude empty grades" "checkbox"
And I press "Save changes"
And I click on "Edit assign Test assignment one" "link"
And I click on "Hidden" "checkbox"
And I press "Save changes"
And I set the following settings for grade item "Course 1":
| Aggregation | Mode of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Mode of grades |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Mode of grades |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment one":
| Hidden | 1 |
And I turn editing mode off
Then I should see "50.00 (50.00 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
And I press "Save changes"
And I log out
@ -289,24 +247,249 @@ Feature: We can use calculated grade totals
And I should see "50.00 (50.00 %)" in the "overview-grade" "table"
@javascript
Scenario: Sum of grades aggregation
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Sum of grades"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Sum of grades"
And I press "Save changes"
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Sum of grades"
And I press "Save changes"
Scenario: Natural aggregation
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 1 |
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Test assignment six":
| Weight adjusted | 1 |
| aggregationcoef2 | 50 |
And I set the following settings for grade item "Test assignment three":
| Extra credit | 1 |
And I turn editing mode off
Then I should see "150.00 (18.99 %)" in the ".course" "css_element"
And I follow "Course grade settings"
And I set the field "Hide totals if they contain hidden items" to "Show totals excluding hidden items"
Then I should see "152.68 (24.43 %)" in the ".course" "css_element"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "report_overview_showtotalsifcontainhidden" to "Show totals excluding hidden items"
And I set the field "report_user_showtotalsifcontainhidden" to "Show totals excluding hidden items"
And I set the field "Show contribution to course total" to "Show"
And I set the field "Show weightings" to "Show"
And I press "Save changes"
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Contribution to course total |
| Test assignment five | 28.57 % | 10.00 (50.00 %) | 020 | 6.43 |
| Test assignment six | 50.00 % | 5.00 (50.00 %) | 010 | 11.25 |
| Test assignment seven | 21.43 % | - | 015 | 0.00 |
| Test assignment eight | 66.67 % | 10.00 (50.00 %) | 020 | 10.00 |
| Test assignment nine | 33.33 % | 5.00 (50.00 %) | 010 | 5.00 |
| Test assignment ten | -( Empty ) | - | 015 | 0.00 |
| Test assignment one | 48.00 % | 60.00 (20.00 %) | 0300 | 60.00 |
| Test assignment two | 16.00 % | 20.00 (20.00 %) | 0100 | 20.00 |
| Test assignment three | 24.00 %( Extra credit ) | 40.00 (26.67 %) | 0150 | 40.00 |
| Test assignment four | 24.00 % | - | 0150 | 0.00 |
And I log out
And I log in as "student1"
And I follow "Course 1"
And I follow "Grades"
And I set the field "Grade report" to "Overview report"
And I should see "110.00 (16.92 %)" in the "overview-grade" "table"
And I should see "113.75 (23.45 %)" in the "overview-grade" "table"
And I set the field "Grade report" to "User report"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Range | Contribution to course total |
| Test assignment six | 70.00 % | 5.00 (50.00 %) | 010 | 8.75 |
| Test assignment seven | 30.00 % | - | 015 | 0.00 |
| Test assignment nine | 100.00 % | 5.00 (50.00 %) | 010 | 5.00 |
| Test assignment ten | -( Empty ) | - | 015 | 0.00 |
| Test assignment one | 61.86 % | 60.00 (20.00 %) | 0300 | 60.0 |
| Test assignment three | 30.93 %( Extra credit ) | 40.00 (26.67 %) | 0150 | 40.0 |
| Test assignment four | 30.93 % | - | 0150 | 0.00 |
@javascript
Scenario: Natural aggregation with drop lowest
When I log out
And I log in as "admin"
And I follow "Course 1"
And I follow "Grades"
And I turn editing mode on
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I navigate to "Set up grades layout" node in "Grade administration > Settings"
And I press "Add category"
And I click on "Show more" "link"
And I set the following fields to these values:
| Category name | Sub category 3 |
| Aggregation | Natural |
| Drop the lowest | 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 1 |
| Grade category | Sub category 3 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 2 |
| Grade category | Sub category 3 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 3 |
| Grade category | Sub category 3 |
And I press "Save changes"
And I follow "Grader report"
And I give the grade "60.00" to the user "Student 1" for the grade item "Manual item 1"
And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
And I give the grade "40.00" to the user "Student 1" for the grade item "Manual item 3"
And I press "Save changes"
And I turn editing mode off
Then I should see "250.00 (25.25 %)" in the ".course" "css_element"
And I turn editing mode on
And I set the following settings for grade item "Manual item 2":
| Extra credit | 1 |
And I turn editing mode off
And I should see "270.00 (27.27 %)" in the ".course" "css_element"
And I turn editing mode on
And I set the following settings for grade item "Manual item 2":
| Extra credit | 0 |
| Maximum grade | 200 |
And I give the grade "21.00" to the user "Student 1" for the grade item "Manual item 2"
And I press "Save changes"
And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
And I press "Save changes"
And I turn editing mode off
And I should see "270.00 (22.69 %)" in the ".course" "css_element"
And I turn editing mode on
And I set the following settings for grade item "Manual item 2":
| Extra credit | 0 |
| Maximum grade | 100 |
And I give the grade "21.00" to the user "Student 1" for the grade item "Manual item 2"
And I press "Save changes"
And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
And I press "Save changes"
And I turn editing mode off
And I should see "250.00 (25.25 %)" in the ".course" "css_element"
And I navigate to "Set up grades layout" node in "Grade administration > Settings"
And I press "Add category"
And I set the following fields to these values:
| Category name | Sub sub category 1 |
| Parent category | Sub category 3 |
And I press "Save changes"
And I follow "Grader report"
And I should see "270.00 (24.77 %)" in the ".course" "css_element"
@javascript
Scenario: Natural aggregation from the setup screen
And I set the field "Grade report" to "Set up grades layout"
And I follow "Edit Course 1"
And I set the field "Aggregation" to "Natural"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I set the field "Aggregation" to "Natural"
And I press "Save changes"
And I follow "Edit Sub category 2"
And I set the field "Aggregation" to "Natural"
And I press "Save changes"
And I set the field "Override weight of Test assignment one" to "1"
And the field "Weight of Test assignment one" matches value "37.975"
And I set the field "Weight of Test assignment one" to "10"
And I set the field "Override weight of Test assignment two" to "1"
And the field "Weight of Test assignment two" matches value "12.658"
And I set the field "Override weight of Test assignment two" to "0"
And I set the field "Override weight of Test assignment six" to "1"
And the field "Weight of Test assignment six" matches value "22.222"
And I set the field "Weight of Test assignment six" to "50"
And I set the field "Override weight of Test assignment six" to "0"
And I set the field "Override weight of Test assignment ten" to "1"
And the field "Weight of Test assignment ten" matches value "33.333"
And I set the field "Weight of Test assignment ten" to "50"
And I set the field "Override weight of Sub category 1" to "1"
And the field "Weight of Sub category 1" matches value "5.696"
And I set the field "Weight of Sub category 1" to "15"
When I press "Save changes"
And I set the field "Override weight of Test assignment two" to "1"
And I set the field "Override weight of Test assignment six" to "1"
Then the field "Weight of Test assignment one" matches value "10.0"
And the field "Weight of Test assignment two" matches value "16.854"
And the field "Weight of Test assignment six" matches value "22.222"
And the field "Weight of Test assignment ten" matches value "50.0"
And the field "Weight of Sub category 1" matches value "15.0"
And I set the field "Override weight of Test assignment one" to "0"
And I set the field "Override weight of Test assignment two" to "0"
And I set the field "Override weight of Test assignment six" to "0"
And I set the field "Override weight of Sub category 1" to "0"
And I press "Save changes"
And I set the field "Override weight of Test assignment one" to "1"
And I set the field "Override weight of Sub category 1" to "1"
And the field "Weight of Test assignment one" matches value "37.975"
And the field "Weight of Sub category 1" matches value "5.696"
And I click on "Reset weights of Sub category 2" "link"
And the field "Weight of Test assignment ten" matches value "33.333"
@javascript
Scenario: Natural aggregation with weights of zero
When I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I turn editing mode off
And I set the field "Grade report" to "Set up grades layout"
And I set the field "Override weight of Test assignment one" to "1"
And I set the field "Weight of Test assignment one" to "0"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment six" to "0"
And I set the field "Override weight of Test assignment nine" to "1"
And I set the field "Weight of Test assignment nine" to "100"
And I press "Save changes"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "report_overview_showtotalsifcontainhidden" to "Show totals excluding hidden items"
And I set the field "report_user_showtotalsifcontainhidden" to "Show totals excluding hidden items"
And I set the field "Show contribution to course total" to "Show"
And I set the field "Show weightings" to "Show"
And I press "Save changes"
Then I should see "75.00 (16.85 %)" in the ".course" "css_element"
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment five | 57.14 % | 10.00 (50.00 %) | 10.00 |
| Test assignment six | 0.00 % | 5.00 (50.00 %) | 0.00 |
| Test assignment seven | 42.86 % | - | 0.00 |
| Test assignment eight | 0.00 % | 10.00 (50.00 %) | 0.00 |
| Test assignment nine | 100.00 % | 5.00 (50.00 %) | 5.00 |
| Test assignment ten | 0.00 % | - | 0.00 |
| Test assignment one | 0.00 % | 60.00 (20.00 %) | 0.00 |
| Test assignment two | 22.47 % | 20.00 (20.00 %) | 20.00 |
| Test assignment three | 33.71 % | 40.00 (26.67 %) | 40.00 |
| Test assignment four | 33.71 % | - | 0.00 |
And I log out
And I log in as "student1"
And I follow "Course 1"
And I follow "Grades"
And I set the field "Grade report" to "Overview report"
And I should see "45.00 (13.85 %)" in the "overview-grade" "table"
And I set the field "Grade report" to "User report"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment six | 0.00 % | 5.00 (50.00 %) | 0.00 |
| Test assignment seven | 100.00 % | - | 0.00 |
| Test assignment nine | 100.00 % | 5.00 (50.00 %) | 5.00 |
| Test assignment ten | 0.00 | - | 0.00 |
| Test assignment one | 0.00 % | 60.00 (20.00 %) | 0.00 |
| Test assignment three | 46.15 % | 40.00 (26.67 %) | 40.00 |
| Test assignment four | 46.15 % | - | 0.00 |

View File

@ -0,0 +1,248 @@
@core @core_grades
Feature: We can understand the gradebook user report
In order to understand the gradebook user report
As an teacher
I need to see the calculated weights for each type of aggregation
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@asd.com | t1 |
| student1 | Student | 1 | student1@asd.com | s1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | idnumber | name | intro |
| assign | C1 | a1 | Test assignment one | Submit something! |
| assign | C1 | a2 | Test assignment two | Submit something! |
| assign | C1 | a3 | Test assignment three | Submit something! |
| assign | C1 | a4 | Test assignment four | Submit something! |
| assign | C1 | a5 | Test assignment five | Submit something! |
| assign | C1 | a6 | Test assignment six | Submit something! |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Grades"
And I turn editing mode on
And I give the grade "60.00" to the user "Student 1" for the grade item "Test assignment one"
And I give the grade "20.00" to the user "Student 1" for the grade item "Test assignment two"
And I give the grade "40.00" to the user "Student 1" for the grade item "Test assignment three"
And I give the grade "10.00" to the user "Student 1" for the grade item "Test assignment four"
And I give the grade "70.00" to the user "Student 1" for the grade item "Test assignment five"
And I give the grade "30.00" to the user "Student 1" for the grade item "Test assignment six"
And I press "Save changes"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Show weightings" to "Show"
And I set the field "Show contribution to course total" to "Show"
And I press "Save changes"
And I set the field "Grade report" to "Set up grades layout"
And I press "Add category"
And I set the field "Category name" to "Sub category"
And I press "Save changes"
And I click on "Move" "link" in the "Test assignment six" "table_row"
# This xpath finds the forth last row in the table.
And I click on "Move to here" "link" in the "//tbody//tr[position()=last()-3]" "xpath_element"
And I click on "Move" "link" in the "Test assignment five" "table_row"
And I click on "Move to here" "link" in the "//tbody//tr[position()=last()-3]" "xpath_element"
And I click on "Move" "link" in the "Test assignment four" "table_row"
And I click on "Move to here" "link" in the "//tbody//tr[position()=last()-3]" "xpath_element"
@javascript
Scenario: Mean of grades aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Mean of grades |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 25.00 % | 60.00 | 15.00 |
| Test assignment two | 25.00 % | 20.00 | 5.00 |
| Test assignment three | 25.00 % | 40.00 | 10.00 |
| Test assignment four | 33.33 % | 10.00 | 0.83 |
| Test assignment five | 33.33 % | 70.00 | 5.83 |
| Test assignment six | 33.33 % | 30.00 | 2.50 |
@javascript
Scenario: Weighted mean of grades aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Weighted mean of grades |
And I set the following settings for grade item "Test assignment one":
| Item weight | 2.0 |
And I set the following settings for grade item "Test assignment two":
| Item weight | 1.0 |
And I set the following settings for grade item "Test assignment three":
| Item weight | 1.0 |
And I set the following settings for grade item "Sub category":
| Item weight | 1.0 |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 40.00 % | 60.00 | 24.00 |
| Test assignment two | 20.00 % | 20.00 | 4.00 |
| Test assignment three | 20.00 % | 40.00 | 8.00 |
| Test assignment four | 33.33 % | 10.00 | 0.67 |
| Test assignment five | 33.33 % | 70.00 | 4.67 |
| Test assignment six | 33.33 % | 30.00 | 2.00 |
@javascript
Scenario: Simple weighted mean of grades aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Simple weighted mean of grades |
And I set the following settings for grade item "Sub category":
| Aggregation | Simple weighted mean of grades |
And I set the following settings for grade item "Test assignment three":
| Extra credit | 1 |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 33.33 % | 60.00 | 20.00 |
| Test assignment two | 33.33 % | 20.00 | 6.67 |
| Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
| Test assignment four | 33.33 % | 10.00 | 1.11 |
| Test assignment five | 33.33 % | 70.00 | 7.78 |
| Test assignment six | 33.33 % | 30.00 | 3.33 |
@javascript
Scenario: Mean of grades (with extra credits) aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Mean of grades (with extra credits) |
And I set the following settings for grade item "Test assignment three":
| Extra credit weight | 1.0 |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 33.33 % | 60.00 | 20.00 |
| Test assignment two | 33.33 % | 20.00 | 6.67 |
| Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
| Test assignment four | 33.33 % | 10.00 | 1.11 |
| Test assignment five | 33.33 % | 70.00 | 7.78 |
| Test assignment six | 33.33 % | 30.00 | 3.33 |
@javascript
Scenario: Median of grades aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Median of grades |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 0.00 % | 60.00 | 0.00 |
| Test assignment two | 0.00 % | 20.00 | 0.00 |
| Test assignment three | 50.00 % | 40.00 | 20.00 |
| Test assignment four | 33.33 % | 10.00 | 1.67 |
| Test assignment five | 33.33 % | 70.00 | 11.67 |
| Test assignment six | 33.33 % | 30.00 | 5.00 |
@javascript
Scenario: Lowest grade aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Lowest grade |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 0.00 % | 60.00 | 0.00 |
| Test assignment two | 100.00 % | 20.00 | 20.00 |
| Test assignment three | 0.00 % | 40.00 | 0.00 |
| Test assignment four | 33.33 % | 10.00 | 0.00 |
| Test assignment five | 33.33 % | 70.00 | 0.00 |
| Test assignment six | 33.33 % | 30.00 | 0.00 |
@javascript
Scenario: Highest grade aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Highest grade |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 100.00 % | 60.00 | 60.00 |
| Test assignment two | 0.00 % | 20.00 | 0.00 |
| Test assignment three | 0.00 % | 40.00 | 0.00 |
| Test assignment four | 33.33 % | 10.00 | 0.00 |
| Test assignment five | 33.33 % | 70.00 | 0.00 |
| Test assignment six | 33.33 % | 30.00 | 0.00 |
@javascript
Scenario: Mode of grades aggregation
And I set the following settings for grade item "Course 1":
| Aggregation | Mode of grades |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 100.00 % | 60.00 | 60.00 |
| Test assignment two | 0.00 % | 20.00 | 0.00 |
| Test assignment three | 0.00 % | 40.00 | 0.00 |
| Test assignment four | 33.33 % | 10.00 | 0.00 |
| Test assignment five | 33.33 % | 70.00 | 0.00 |
| Test assignment six | 33.33 % | 30.00 | 0.00 |
@javascript
Scenario: View user report with mixed aggregation methods
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
And I set the following settings for grade item "Sub category":
| Aggregation | Weighted mean of grades |
And I set the following settings for grade item "Test assignment three":
| Extra credit | 1 |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 33.33 % | 60.00 | 60.00 |
| Test assignment two | 33.33 % | 20.00 | 20.00 |
| Test assignment three | 33.33 %( Extra credit ) | 40.00 | 40.00 |
| Test assignment four | 33.33 % | 10.00 | 3.33 |
| Test assignment five | 33.33 % | 70.00 | 23.33 |
| Test assignment six | 33.33 % | 30.00 | 10.00 |
| Category totalWeighted mean of grades. | 33.33 % | 36.67 | - |
| Course totalNatural. | - | 156.67 | - |
@javascript
Scenario: View user report with natural aggregation
And I set the following settings for grade item "Test assignment three":
| Extra credit | 1 |
And I set the field "Grade report" to "User report"
And I set the field "Select all or one user" to "Student 1"
# Check the values in the weights column.
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Test assignment one | 20.00 % | 60.00 | 60.00 |
| Test assignment two | 20.00 % | 20.00 | 20.00 |
| Test assignment three | 20.00 %( Extra credit ) | 40.00 | 40.00 |
| Test assignment four | 33.33 % | 10.00 | 10.00 |
| Test assignment five | 33.33 % | 70.00 | 70.00 |
| Test assignment six | 33.33 % | 30.00 | 30.00 |
| Category totalNatural. | 60.00 % | 110.00 | - |
| Course totalNatural. | - | 230.00 | - |

View File

@ -0,0 +1,122 @@
@core @core_grades
Feature: We can use a minimum grade different than zero
In order to use a minimum grade different than zero
As an teacher
I need to set up a minimum grade different than zero
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@asd.com | t1 |
| student1 | Student | 1 | student1@asd.com | s1 |
| student2 | Student | 2 | student2@asd.com | s2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
| Sub category 2 | C1 |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I am on homepage
And I follow "Course 1"
And I follow "Grades"
And I navigate to "Set up grades layout" node in "Grade administration > Settings"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 1 |
| Minimum grade | -100 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 2 |
| Minimum grade | 50 |
| Grade category | Course 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 3 |
| Maximum grade | 50 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 4 |
| Minimum grade | -100 |
| Grade category | Sub category 1 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 5 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I press "Add grade item"
And I set the following fields to these values:
| Item name | Manual item 6 |
| Minimum grade | 50 |
| Grade category | Sub category 2 |
And I press "Save changes"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the field "Show weightings" to "Show"
And I set the field "Show contribution to course total" to "Show"
And I press "Save changes"
@javascript
Scenario: Natural aggregation with negative and positive grade
And I navigate to "Set up grades layout" node in "Grade administration > Settings"
And I set the following settings for grade item "Sub category 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Sub category 2":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I set the following settings for grade item "Course 1":
| Aggregation | Natural |
| Exclude empty grades | 0 |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Grades"
And I turn editing mode on
When I give the grade "-25.00" to the user "Student 1" for the grade item "Manual item 1"
And I give the grade "50.00" to the user "Student 1" for the grade item "Manual item 2"
And I give the grade "-80.00" to the user "Student 1" for the grade item "Manual item 3"
And I give the grade "-10.00" to the user "Student 1" for the grade item "Manual item 4"
And I give the grade "50.00" to the user "Student 1" for the grade item "Manual item 5"
And I give the grade "75.00" to the user "Student 1" for the grade item "Manual item 6"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 1"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 2"
And I give the grade "-10.00" to the user "Student 2" for the grade item "Manual item 3"
And I give the grade "50.00" to the user "Student 2" for the grade item "Manual item 4"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 5"
And I give the grade "0.00" to the user "Student 2" for the grade item "Manual item 6"
And I press "Save changes"
And I follow "User report"
And I set the field "Select all or one user" to "Student 1"
Then the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Manual item 1 | 18.18 % | -25.00 | -25.00 |
| Manual item 2 | 18.18 % | 50.00 | 50.00 |
| Manual item 3 | 33.33 % | -80.00 | -80.00 |
| Manual item 4 | 66.67 % | -10.00 | -10.00 |
| Manual item 5 | 50.00 % | 50.00 | 50.00 |
| Manual item 6 | 50.00 % | 75.00 | 75.00 |
And I set the field "Select all or one user" to "Student 2"
And the following should exist in the "user-grade" table:
| Grade item | Calculated weight | Grade | Contribution to course total |
| Manual item 1 | 18.18 % | 0.00 | 0.00 |
| Manual item 2 | 18.18 % | 50.00 | 50.00 |
| Manual item 3 | 33.33 % | -10.00 | -10.00 |
| Manual item 4 | 66.67 % | 50.00 | 50.00 |
| Manual item 5 | 50.00 % | 50.00 | 50.00 |
| Manual item 6 | 50.00 % | 50.00 | 50.00 |

View File

@ -0,0 +1,252 @@
@core @core_grades
Feature: We can use natural aggregation and weights will be normalised to a total of one hundred
In order to override weights
As a teacher
I need to add assessments to the gradebook.
Background:
Given the following "courses" exist:
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@asd.com | t1 |
| student1 | Student | 1 | student1@asd.com | s1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | grade |
| assign | C1 | a1 | Test assignment one | Submit something! | 300 |
| assign | C1 | a2 | Test assignment two | Submit something! | 100 |
| assign | C1 | a3 | Test assignment three | Submit something! | 150 |
| assign | C1 | a4 | Test assignment four | Submit nothing! | 150 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | gradecategory | grade |
| assign | C1 | a5 | Test assignment five | Submit something! | Sub category 1 | 20 |
| assign | C1 | a6 | Test assignment six | Submit something! | Sub category 1 | 10 |
| assign | C1 | a7 | Test assignment seven | Submit nothing! | Sub category 1 | 15 |
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Grades"
And I set the field "Grade report" to "Set up grades layout"
@javascript
Scenario: Setting all weights in a category to exactly one hundred in total.
And the field "Weight of Test assignment five" matches value "44.444"
And the field "Weight of Test assignment six" matches value "22.222"
And the field "Weight of Test assignment seven" matches value "33.333"
When I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment five" to "30"
And I set the field "Weight of Test assignment six" to "50"
And I set the field "Weight of Test assignment seven" to "20"
And I press "Save changes"
Then I should not see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "30.0"
And the field "Weight of Test assignment six" matches value "50.0"
And the field "Weight of Test assignment seven" matches value "20.0"
@javascript
Scenario: Setting all weights in a category to less than one hundred is normalised.
When I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment five" to "1"
And I set the field "Weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment seven" to "2"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "25.0"
And the field "Weight of Test assignment six" matches value "25.0"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: Set one of the grade item weights to a figure over one hundred.
When I set the field "Override weight of Test assignment five" to "1"
And I set the field "Weight of Test assignment five" to "120"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "100.0"
And the field "Weight of Test assignment six" matches value "0.0"
And the field "Weight of Test assignment seven" matches value "0.0"
@javascript
Scenario: Setting several but not all grade item weights to over one hundred each.
When I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment five" to "150"
And I set the field "Weight of Test assignment six" to "150"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "50.000"
And the field "Weight of Test assignment six" matches value "50.000"
And the field "Weight of Test assignment seven" matches value "0.0"
@javascript
Scenario: Grade items weights are normalised when all grade item weights are overridden (sum exactly 100). Extra credit is set to zero.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
And I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment five" to "60"
And I set the field "Weight of Test assignment six" to "40"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "60.000"
And the field "Weight of Test assignment six" matches value "40.000"
And the field "Weight of Test assignment seven" matches value "0.0"
And I follow "Reset weights of Sub category 1"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: Grade items weights are normalised when all grade item weights are overridden (sum over 100). Extra credit is set to zero.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment five" to "60"
And I set the field "Weight of Test assignment six" to "50"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "54.545"
And the field "Weight of Test assignment six" matches value "45.455"
And the field "Weight of Test assignment seven" matches value "0.0"
And I follow "Reset weights of Sub category 1"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: Grade items weights are normalised when all grade item weights are overridden (sum under 100). Extra credit is set to zero.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment five" to "1"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Weight of Test assignment five" to "40"
And I set the field "Weight of Test assignment six" to "30"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "57.143"
And the field "Weight of Test assignment six" matches value "42.857"
And the field "Weight of Test assignment seven" matches value "0.0"
And I follow "Reset weights of Sub category 1"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: Grade items weights are normalised when not all grade item weights are overridden. Extra credit is set respectful to non-overridden items.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment five" to "1"
And I set the field "Weight of Test assignment five" to "40"
And I press "Save changes"
Then I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "40.00"
And the field "Weight of Test assignment six" matches value "60.000"
And the field "Weight of Test assignment seven" matches value "90.0"
And I follow "Reset weights of Sub category 1"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: The extra credit grade item weight is overridden to a figure over one hundred and then
the grade item is set to normal.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment seven" to "105"
And I press "Save changes"
Then I should not see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "105.0"
And I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "0"
And I press "Save changes"
And I should see "Your weights have been adjusted to total 100."
And the field "Weight of Test assignment five" matches value "0.0"
And the field "Weight of Test assignment six" matches value "0.0"
And the field "Weight of Test assignment seven" matches value "100.0"
@javascript
Scenario: The extra credit grade item weight is overridden to a figure over one hundred and then
the grade category is reset.
When I follow "Edit assign Test assignment seven"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment seven" to "105"
And I press "Save changes"
And I follow "Reset weights of Sub category 1"
And the field "Weight of Test assignment five" matches value "66.667"
And the field "Weight of Test assignment six" matches value "33.333"
And the field "Weight of Test assignment seven" matches value "50.0"
@javascript
Scenario: Two out of three grade items weights are overridden and one is not.
The overridden grade item weights total over one hundred.
Given I set the field "Override weight of Test assignment six" to "1"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment six" to "55"
And I set the field "Weight of Test assignment seven" to "65"
And I press "Save changes"
And I should see "Your weights have been adjusted to total 100."
Then the field "Weight of Test assignment five" matches value "0.0"
And the field "Weight of Test assignment six" matches value "45.833"
And the field "Weight of Test assignment seven" matches value "54.167"
@javascript
Scenario: With one grade item set as extra credit, when I reset the weights for a category they return to the natural weights.
When I follow "Edit assign Test assignment five"
And I set the field "Extra credit" to "1"
And I press "Save changes"
And I set the field "Override weight of Test assignment six" to "1"
And I set the field "Override weight of Test assignment seven" to "1"
And I set the field "Weight of Test assignment six" to "55"
And I set the field "Weight of Test assignment seven" to "40"
And I press "Save changes"
And I follow "Reset weights of Sub category 1"
Then the field "Weight of Test assignment five" matches value "80.0"
And the field "Weight of Test assignment six" matches value "40.0"
And the field "Weight of Test assignment seven" matches value "60.0"

View File

@ -0,0 +1,163 @@
@core @core_grades
Feature: View gradebook when scales are used
In order to use scales to grade activities
As an teacher
I need to be able to view gradebook with scales
Background:
Given I log in as "admin"
And I set the following administration settings values:
| grade_report_showranges | 1 |
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I navigate to "Scales" node in "Site administration > Grades"
And I press "Add a new scale"
And I set the following fields to these values:
| Name | Letterscale |
| Scale | F,D,C,B,A |
And I press "Save changes"
And I log out
And the following "courses" exist:
| fullname | shortname |
| Course 1 | C1 |
And the following "users" exist:
| username | firstname | lastname | email | idnumber |
| teacher1 | Teacher | 1 | teacher1@asd.com | t1 |
| student1 | Student | 1 | student1@asd.com | s1 |
| student2 | Student | 2 | student2@asd.com | s2 |
| student3 | Student | 3 | student3@asd.com | s3 |
| student4 | Student | 4 | student4@asd.com | s4 |
| student5 | Student | 5 | student5@asd.com | s5 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| student4 | C1 | student |
| student5 | C1 | student |
And the following "grade categories" exist:
| fullname | course |
| Sub category 1 | C1 |
And the following "activities" exist:
| activity | course | idnumber | name | intro | gradecategory |
| assign | C1 | a1 | Test assignment one | Submit something! | Sub category 1 |
And I log in as "teacher1"
And I follow "Course 1"
And I follow "Test assignment one"
And I follow "Edit settings"
And I expand all fieldsets
And I set the field "grade[modgrade_type]" to "Scale"
And I set the field "grade[modgrade_scale]" to "Letterscale"
And I press "Save and display"
And I follow "View/grade all submissions"
And I click on "Grade Student 1" "link" in the "Student 1" "table_row"
And I set the field "Grade" to "A"
And I press "Save and show next"
And I set the field "Grade" to "B"
And I press "Save and show next"
And I set the field "Grade" to "C"
And I press "Save and show next"
And I set the field "Grade" to "D"
And I press "Save and show next"
And I set the field "Grade" to "F"
And I press "Save changes"
And I follow "Course 1"
And I follow "Grades"
And I turn editing mode on
@javascript
Scenario: Test displaying scales in gradebook in aggregation method Natural
When I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -4- | -5- | -6- |
| Student 1 | A | 5.00 | 5.00 |
| Student 2 | B | 4.00 | 4.00 |
| Student 3 | C | 3.00 | 3.00 |
| Student 4 | D | 2.00 | 2.00 |
| Student 5 | F | 1.00 | 1.00 |
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- |
| Range | FA | 0.005.00 | 0.005.00 |
| Overall average | C | 3.00 | 3.00 |
And I follow "User report"
And I set the field "Select all or one user" to "Student 3"
And I click on "Select all or one user" "select"
And the following should exist in the "user-grade" table:
| Grade item | Grade | Range | Percentage |
| Test assignment one | C | FA | 50.00 % |
| Category totalNatural. | 3.00 | 05 | 60.00 % |
| Course totalNatural. | 3.00 | 05 | 60.00 % |
And I set the field "jump" to "Set up grades layout"
And the following should exist in the "grade_edit_tree_table" table:
| Name | Max grade |
| Test assignment one | 5.00 |
| Category total | 5.00 |
| Course total | 5.00 |
And I log out
And I log in as "student2"
And I follow "Course 1"
And I follow "Grades"
And the following should exist in the "user-grade" table:
| Grade item | Grade | Range | Percentage |
| Test assignment one | B | FA | 75.00 % |
| Category totalNatural. | 4.00 | 05 | 80.00 % |
| Course totalNatural. | 4.00 | 05 | 80.00 % |
@javascript
Scenario Outline: Test displaying scales in gradebook in all other aggregation methods
When I follow "Edit Course 1"
And I set the field "Aggregation" to "<aggregation>"
And I press "Save changes"
And I follow "Edit Sub category 1"
And I expand all fieldsets
And I set the field "Aggregation" to "<aggregation>"
And I set the field "Category name" to "Sub category (<aggregation>)"
And I set the field "Maximum grade" to "5"
And I set the field "Minimum grade" to "1"
And I press "Save changes"
And I turn editing mode off
Then the following should exist in the "user-grades" table:
| -1- | -4- | -5- | -6- |
| Student 1 | A | 5.00 | <coursetotal1> |
| Student 2 | B | 4.00 | <coursetotal2> |
| Student 3 | C | 3.00 | <coursetotal3> |
| Student 4 | D | 2.00 | <coursetotal4> |
| Student 5 | F | 1.00 | <coursetotal5> |
And the following should exist in the "user-grades" table:
| -1- | -2- | -3- | -4- |
| Range | FA | 1.005.00 | 0.00100.00 |
| Overall average | C | 3.00 | <overallavg> |
And I follow "User report"
And I set the field "Select all or one user" to "Student 3"
And I click on "Select all or one user" "select"
And the following should exist in the "user-grade" table:
| Grade item | Grade | Range | Percentage |
| Test assignment one | C | FA | 50.00 % |
| Category total<aggregation>. | 3.00 | 15 | 50.00 % |
| Course total<aggregation>. | <coursetotal3> | 0100 | <courseperc3> |
And I set the field "jump" to "Set up grades layout"
And the following should exist in the "grade_edit_tree_table" table:
| Name | Max grade |
| Test assignment one | A (5) |
| Category total | |
| Course total | |
And I log out
And I log in as "student2"
And I follow "Course 1"
And I follow "Grades"
And the following should exist in the "user-grade" table:
| Grade item | Grade | Range | Percentage |
| Test assignment one | B | FA | 75.00 % |
| Category total<aggregation>. | 4.00 | 15 | 75.00 % |
| Course total<aggregation>. | <coursetotal2> | 0100 | <courseperc2> |
Examples:
| aggregation | coursetotal1 | coursetotal2 | coursetotal3 | coursetotal4 | coursetotal5 |overallavg | courseperc2 | courseperc3 |
| Mean of grades | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Weighted mean of grades | - | - | - | - | - | - | - | - |
| Simple weighted mean of grades | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Mean of grades (with extra credits) | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Median of grades | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Lowest grade | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Highest grade | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |
| Mode of grades | 100.00 | 75.00 | 50.00 | 25.00 | 0.00 | 50.00 | 75.00 % | 50.00 % |

View File

@ -17,6 +17,10 @@ Feature: We can enter in grades and view reports from the gradebook
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And I log in as "admin"
And I set the following administration settings values:
| grade_aggregations_visible | Mean of grades,Weighted mean of grades,Simple weighted mean of grades,Mean of grades (with extra credits),Median of grades,Lowest grade,Highest grade,Mode of grades,Natural |
And I log out
And I log in as "teacher1"
And I follow "Course 1"
And I turn editing mode on
@ -68,23 +72,23 @@ Feature: We can enter in grades and view reports from the gradebook
| Grade item | Grade | Range | Percentage |
| Test assignment name 1 | 80.00 | 0100 | 80.00 % |
| Test assignment name 2 | 90.00 | 0100 | 90.00 % |
| Course total | 85.00 | 0100 | 85.00 % |
| Course totalNatural. | 170.00 | 0200 | 85.00 % |
And the following should not exist in the "user-grade" table:
| Grade item | Grade | Range | Percentage |
| Course total | 90.00 | 0110 | 90.00 % |
| Course totalNatural. | 90.00 | 0100 | 90.00 % |
And I set the field "Grade report" to "Overview report"
And "C1" row "Grade" column of "overview-grade" table should contain "85.00"
And "C1" row "Grade" column of "overview-grade" table should contain "170.00"
And "C1" row "Grade" column of "overview-grade" table should not contain "90.00"
@javascript
Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
When I set the field "Grade report" to "Full view"
And I set the field "Aggregation" to "Weighted mean of grades"
And I set the following fields to these values:
| Extra credit value for Test assignment name | 0.72 |
When I set the field "Grade report" to "Set up grades layout"
And I set the following settings for grade item "Course 1":
| Aggregation | Weighted mean of grades |
And I set the field "Extra credit value for Test assignment name" to "0.72"
And I press "Save changes"
And I set the field "Grade report" to "User report"
And I follow "Course grade settings"
And I navigate to "Course grade settings" node in "Grade administration > Settings"
And I set the following fields to these values:
| Show weightings | Show |
And I press "Save changes"
@ -93,12 +97,12 @@ Feature: We can enter in grades and view reports from the gradebook
And I follow "Course 1"
And I follow "Grades"
Then the following should exist in the "user-grade" table:
| Grade item | Weight | Grade | Range | Percentage |
| Test assignment name 1 | 0.72 | 80.00 | 0100 | 80.00 % |
| Test assignment name 2 | 1.00 | 90.00 | 0100 | 90.00 % |
| Course total | - | 85.81 | 0100 | 85.81 % |
| Grade item | Calculated weight | Grade | Range | Percentage |
| Test assignment name 1 | 41.86 % | 80.00 | 0100 | 80.00 % |
| Test assignment name 2 | 58.14 % | 90.00 | 0100 | 90.00 % |
| Course totalWeighted mean of grades. | - | 85.81 | 0100 | 85.81 % |
And the following should not exist in the "user-grade" table:
| Grade item | Weight | Percentage |
| Grade item | Calculated weight | Percentage |
| Test assignment name 1 | 0.72% | 0.72% |
| Test assignment name 2 | 1.00% | 1.00% |
| Course total | 1.00% | 1.00% |

View File

@ -93,7 +93,10 @@ class core_grade_edittreelib_testcase extends advanced_testcase {
// Make the expected scale text.
$scaleitems = null;
$scaleitems = explode(',', $scale->scale);
$scalestring = end($scaleitems) . ' (' . count($scaleitems) . ')';
// Make sure that we expect grademax (displayed in parenthesis) be the same
// as number of items in the scale.
$scalestring = end($scaleitems) . ' (' .
format_float(count($scaleitems), 2) . ')';
$this->assertEquals(GRADE_TYPE_SCALE, $gradeitem->gradetype);
$this->assertEquals($scale->id, $gradeitem->scaleid);

View File

@ -121,17 +121,17 @@ class core_grade_report_graderlib_testcase extends advanced_testcase {
$this->assertEquals($emptypreferences, $report->collapsed);
// Validating preferences set/get for one course.
$report->process_action('c13', 'switch_minus');
$report->process_action('cg13', 'switch_minus');
$report = $this->create_report($course1);
$this->assertEquals(array(13), $report->collapsed['aggregatesonly']);
$this->assertEmpty($report->collapsed['gradesonly']);
$report->process_action('c13', 'switch_plus');
$report->process_action('cg13', 'switch_plus');
$report = $this->create_report($course1);
$this->assertEmpty($report->collapsed['aggregatesonly']);
$this->assertEquals(array(13), $report->collapsed['gradesonly']);
$report->process_action('c13', 'switch_whole');
$report->process_action('cg13', 'switch_whole');
$report = $this->create_report($course1);
$this->assertEquals($emptypreferences, $report->collapsed);
@ -146,18 +146,18 @@ class core_grade_report_graderlib_testcase extends advanced_testcase {
$report1 = $this->create_report($course1);
foreach ($course1cats as $catid) {
$report1->process_action('c'.$catid, 'switch_minus');
$report1->process_action('cg'.$catid, 'switch_minus');
}
$report2 = $this->create_report($course2);
foreach ($course2cats as $catid) {
$report2->process_action('c'.$catid, 'switch_minus');
$report2->process_action('c'.$catid, 'switch_plus');
$report2->process_action('cg'.$catid, 'switch_minus');
$report2->process_action('cg'.$catid, 'switch_plus');
}
$report3 = $this->create_report($course3);
foreach ($course3cats as $catid) {
$report3->process_action('c'.$catid, 'switch_minus');
$report3->process_action('cg'.$catid, 'switch_minus');
if (($i++)%2) {
$report3->process_action('c'.$catid, 'switch_plus');
$report3->process_action('cg'.$catid, 'switch_plus');
}
}
@ -201,7 +201,7 @@ class core_grade_report_graderlib_testcase extends advanced_testcase {
set_user_preference('grade_report_grader_collapsed_categories'.$course1->id, json_encode($toobigvalue));
$report1 = $this->create_report($course1);
$report1->process_action('c'.$lastvalue, 'switch_minus');
$report1->process_action('cg'.$lastvalue, 'switch_minus');
$report1 = $this->create_report($course1);
$this->assertEquals($expectedvalue, $report1->collapsed);
@ -218,7 +218,7 @@ class core_grade_report_graderlib_testcase extends advanced_testcase {
$toobigvalue['aggregatesonly'][] = $lastcatid;
$report1 = $this->create_report($course1);
$report1->process_action('c'.$lastcatid, 'switch_minus');
$report1->process_action('cg'.$lastcatid, 'switch_minus');
// One last value should be removed from both arrays.
$report1 = $this->create_report($course1);

View File

@ -124,21 +124,26 @@ class core_grade_reportlib_testcase extends advanced_testcase {
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => $datagrade + $forumgrade,
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
// Should blank the student total as course grade depends on a hidden item.
$report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN);
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => null,
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
// Should return the course total minus the hidden database activity grade.
$report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_SHOW_TOTAL_IF_CONTAINS_HIDDEN);
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => $forumgrade,
$this->assertEquals(array('grade' => floatval($forumgrade),
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
// Note: we cannot simply hide modules and call $report->blank_hidden_total() again.
// It stores grades in a static variable so $report->blank_hidden_total() will return incorrect totals
@ -195,14 +200,18 @@ class core_grade_reportlib_testcase extends advanced_testcase {
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => $datagrade + $forumgrade,
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
// Should blank the student total as course grade depends on a hidden item.
$report->showtotalsifcontainhidden = array($course->id => GRADE_REPORT_HIDE_TOTAL_IF_CONTAINS_HIDDEN);
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => null,
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
// Should return the course total minus the hidden activity grades.
// They are both hidden so should return null.
@ -210,6 +219,8 @@ class core_grade_reportlib_testcase extends advanced_testcase {
$result = $report->blank_hidden_total_and_adjust_bounds($course->id, $coursegradeitem, $datagrade + $forumgrade);
$this->assertEquals(array('grade' => null,
'grademax' => $coursegradeitem->grademax,
'grademin' => $coursegradeitem->grademin), $result);
'grademin' => $coursegradeitem->grademin,
'aggregationstatus' => 'unknown',
'aggregationweight' => null), $result);
}
}

View File

@ -4,3 +4,8 @@ hidden,core_portfolio
hidden,core_question
hidden,core_repository
hidden,core_role
categoriesanditems,core_grades
simpleview,core_grades
fullview,core_grades
categoriesedit,core_grades
edittree,core_grades

View File

@ -34,12 +34,14 @@ $string['additem'] = 'Add grade item';
$string['addoutcome'] = 'Add an outcome';
$string['addoutcomeitem'] = 'Add outcome item';
$string['addscale'] = 'Add a scale';
$string['adjustedweight'] = 'Weight adjusted';
$string['aggregateextracreditmean'] = 'Mean of grades (with extra credits)';
$string['aggregatemax'] = 'Highest grade';
$string['aggregatemean'] = 'Mean of grades';
$string['aggregatemedian'] = 'Median of grades';
$string['aggregatemin'] = 'Lowest grade';
$string['aggregatemode'] = 'Mode of grades';
$string['aggregatenotonlygraded'] = 'Include empty grades';
$string['aggregateonlygraded'] = 'Exclude empty grades';
$string['aggregateonlygraded_help'] = 'An empty grade is a grade which is missing from the gradebook. It may be from an assignment submission which has not yet been graded or from a quiz which has not yet been attempted etc.
@ -48,8 +50,9 @@ $string['aggregateoutcomes'] = 'Include outcomes in aggregation';
$string['aggregateoutcomes_help'] = 'If enabled, outcomes are included in the aggregation. This may result in an unexpected category total.';
$string['aggregatesonly'] = 'Aggregates only';
$string['aggregatesubcats'] = 'Aggregate including subcategories';
$string['aggregatesubcatsshort'] = 'Include subcategories';
$string['aggregatesubcats_help'] = 'This setting determines whether grades in subcategories are included in the aggregation.';
$string['aggregatesum'] = 'Sum of grades';
$string['aggregatesum'] = 'Natural';
$string['aggregateweightedmean'] = 'Weighted mean of grades';
$string['aggregateweightedmean2'] = 'Simple weighted mean of grades';
$string['aggregation'] = 'Aggregation';
@ -60,15 +63,20 @@ $string['aggregation_help'] = 'The aggregation determines how grades in a catego
* Lowest grade
* Highest grade
* Mode of grades - The grade that occurs the most frequently
* Sum of grades - The sum of all grade values, with scale grades being ignored';
* Natural - The sum of all grade values scaled by weight';
$string['aggregationhintnovalue'] = '( Empty )';
$string['aggregationhintdropped'] = '( Dropped )';
$string['aggregationhintexcluded'] = '( Excluded )';
$string['aggregationhintextra'] = '( Extra credit )';
$string['aggregation_link'] = 'grade/aggregation';
$string['aggregationcoef'] = 'Aggregation coefficient';
$string['aggregationcoefextra'] = 'Extra credit'; // for the header of the table at Edit categories and items page
$string['aggregationcoefextra_help'] = 'If the aggregation is Sum of grades or Simple weighted mean and the extra credit checkbox is ticked, the grade item\'s maximum grade is not added to the category\'s maximum grade, resulting in the possibility of achieving the maximum grade (or grades over the maximum if enabled by the site administrator) in the category without having the maximum grade in all the grade items.
$string['aggregationcoefextra_help'] = 'If the aggregation is Natural or Simple weighted mean and the extra credit checkbox is ticked, the grade item\'s maximum grade is not added to the category\'s maximum grade, resulting in the possibility of achieving the maximum grade (or grades over the maximum if enabled by the site administrator) in the category without having the maximum grade in all the grade items.
If the aggregation is Mean of grades (with extra credits) and the extra credit is set to a value greater than zero, the extra credit is the factor by which the grade is multiplied before adding it to the total after the computation of the mean.';
$string['aggregationcoefextra_link'] = 'grade/aggregation';
$string['aggregationcoefextrasum'] = 'Extra credit'; // for the form with checkboxes: Sum of grades or Simple weighted mean
$string['aggregationcoefextrasum'] = 'Extra credit'; // for the form with checkboxes: Natural or Simple weighted mean
$string['aggregationcoefextrasumabbr'] = '+';
$string['aggregationcoefextrasum_help'] = 'If the extra credit checkbox is ticked, the grade item\'s maximum grade is not added to the category\'s maximum grade, resulting in the possibility of achieving the maximum grade (or grades over the maximum if enabled by the site administrator) in the category without having the maximum grade in all the grade items.';
$string['aggregationcoefextrasum_link'] = 'grade/aggregation';
$string['aggregationcoefextraweight'] = 'Extra credit weight'; // for the form with input: Mean of grades (with extra credits) only
@ -77,6 +85,7 @@ $string['aggregationcoefextraweight_link'] = 'grade/aggregation';
$string['aggregationcoefweight'] = 'Item weight';
$string['aggregationcoefweight_help'] = 'The item weight is used in the category aggregation to influence the importance of the item compared with other grade items in the same category.';
$string['aggregationcoefweight_link'] = 'grade/aggregation';
$string['aggregationofa'] = 'Aggregation of {$a}';
$string['aggregationposition'] = 'Aggregation position';
$string['aggregationposition_help'] = 'This setting determines whether the category and course total columns are displayed first or last in the gradebook reports.';
$string['aggregationsvisible'] = 'Available aggregation types';
@ -107,8 +116,6 @@ $string['calculationsaved'] = 'Calculation saved';
$string['calculationview'] = 'View calculation';
$string['cannotaccessgroup'] = 'Can not access grades of selected group, sorry.';
$string['categories'] = 'Categories';
$string['categoriesanditems'] = 'Setup';
$string['categoriesedit'] = 'Edit setup';
$string['category'] = 'Category';
$string['categoryedit'] = 'Edit category';
$string['categoryname'] = 'Category name';
@ -119,6 +126,7 @@ $string['combo'] = 'Tabs and Dropdown menu';
$string['compact'] = 'Compact';
$string['componentcontrolsvisibility'] = 'Whether this grade item is hidden is controlled by the activity settings.';
$string['contract'] = 'Contract category';
$string['contributiontocoursetotal'] = 'Contribution to course total';
$string['controls'] = 'Controls';
$string['courseavg'] = 'Course average';
$string['coursegradecategory'] = 'Course grade category';
@ -152,6 +160,7 @@ $string['droplow'] = 'Drop the lowest';
$string['droplow_help'] = 'This setting enables a specified number of the lowest grades to be excluded from the aggregation.';
$string['droplowestvalue'] = 'Set drop lowest grade value';
$string['dropped'] = 'Dropped';
$string['droplowestvalues'] = 'Drop {$a} lowest values';
$string['dropxlowest'] = 'Drop X lowest';
$string['dropxlowestwarning'] = 'Note: If you use drop x lowest the grading assumes that all items in the category have the same point value. If point values differ results will be unpredictable';
$string['duplicatescale'] = 'Duplicate scale';
@ -164,7 +173,6 @@ $string['editgradeletters'] = 'Edit grade letters';
$string['editoutcome'] = 'Edit outcome';
$string['editoutcomes'] = 'Edit outcomes';
$string['editscale'] = 'Edit scale';
$string['edittree'] = 'Setup';
$string['editverbose'] = 'Edit {$a->category} {$a->itemmodule} {$a->itemname}';
$string['enableajax'] = 'Enable AJAX';
$string['enableajax_help'] = 'Adds a layer of AJAX functionality to the grader report, simplifying and speeding up common operations. Depends on Javascript being switched on at the user\'s browser level.';
@ -220,7 +228,6 @@ $string['forelementtypes'] = 'for the selected {$a}';
$string['forstudents'] = 'For students';
$string['full'] = 'Full';
$string['fullmode'] = 'Full view';
$string['fullview'] = 'Full view';
$string['generalsettings'] = 'General settings';
$string['grade'] = 'Grade';
$string['gradeadministration'] = 'Grade administration';
@ -388,6 +395,7 @@ $string['itemnamehelp'] = 'The name of this item, pushed in by the module.';
$string['items'] = 'Items';
$string['itemsedit'] = 'Edit grade item';
$string['keephigh'] = 'Keep the highest';
$string['keephighestvalues'] = 'Keep the {$a} highest values';
$string['keephigh_help'] = 'If set, this option will only keep the X highest grades, X being the selected value for this option.';
$string['keymanager'] = 'Key manager';
$string['lessthanmin'] = 'The grade entered for {$a->itemname} for {$a->username} is less than the minimum allowed';
@ -462,6 +470,7 @@ $string['nonunlockableverbose'] = 'This grade cannot be unlocked until {$a->item
$string['nonweightedpct'] = 'non-weighted %';
$string['nooutcome'] = 'No outcome';
$string['nooutcomes'] = 'Outcome items must be linked to a course outcome, but there are no outcomes for this course. Would you like to add one?';
$string['nopermissiontoresetweights'] = 'No permission to reset the weights';
$string['nopublish'] = 'Do not publish';
$string['norolesdefined'] = 'No roles defined in Administration > Grades > General settings > Graded roles';
$string['noscales'] = 'Outcomes must be linked to a course scale or global scale, but there are none. Would you like to add one?';
@ -507,6 +516,7 @@ When a grade is edited in the grader report, the overridden checkbox is ticked a
$string['overriddennotice'] = 'Your final grade from this activity was manually adjusted.';
$string['overridesitedefaultgradedisplaytype'] = 'Override site defaults';
$string['overridesitedefaultgradedisplaytype_help'] = 'If ticked, grade letters and boundaries for the course may be set, rather than using the site defaults.';
$string['overrideweightofa'] = 'Override weight of {$a}';
$string['parentcategory'] = 'Parent category';
$string['pctoftotalgrade'] = '% of total grade';
$string['percent'] = 'Percent';
@ -560,6 +570,7 @@ $string['reportdefault'] = 'Report default ({$a})';
$string['reportplugins'] = 'Report plugins';
$string['reportsettings'] = 'Report settings';
$string['reprintheaders'] = 'Reprint headers';
$string['resetweights'] = 'Reset weights of {$a->itemname}';
$string['respectingcurrentdata'] = 'leaving current configuration unmodified';
$string['rowpreviewnum'] = 'Preview rows';
$string['savechanges'] = 'Save changes';
@ -582,12 +593,15 @@ $string['setgradeletters'] = 'Set grade letters';
$string['setpreferences'] = 'Set preferences';
$string['setting'] = 'Setting';
$string['settings'] = 'Settings';
$string['setupgradeslayout'] = 'Set up grades layout';
$string['setweights'] = 'Set weights';
$string['showanalysisicon'] = 'Show grade analysis icon';
$string['showanalysisicon_desc'] = 'Whether to show grade analysis icon by default. If the activity module supports it, the grade analysis icon links to a page with more detailed explanation of the grade and how it was obtained.';
$string['showanalysisicon_help'] = 'If the activity module supports it, the grade analysis icon links to a page with more detailed explanation of the grade and how it was obtained.';
$string['showaverage'] = 'Show average';
$string['showaverage_help'] = 'Show the average column? Students may be able to estimate other student\'s grades if the average is calculated from a small number of grades. For performance reasons the average is approximate if it is dependent on any hidden items.';
$string['showcontributiontocoursetotal'] = 'Show contribution to course total';
$string['showcontributiontocoursetotal_help'] = 'Show an additional column containing the calculated contribution to the course total?';
$string['showfeedback'] = 'Show feedback';
$string['showfeedback_help'] = 'Show the feedback column?';
$string['showgrade'] = 'Show grades';
@ -639,7 +653,6 @@ $string['showrank_help'] = 'Show the position of the student in relation to the
$string['showuserimage'] = 'Show user profile images';
$string['showuserimage_help'] = 'Whether to show the user\'s profile image next to the name in the grader report.';
$string['showverbose'] = 'Show {$a->category} {$a->itemmodule} {$a->itemname}';
$string['simpleview'] = 'Simple view';
$string['sitewide'] = 'Site-wide';
$string['sort'] = 'sort';
$string['sortasc'] = 'Sort in ascending order';
@ -656,6 +669,8 @@ $string['studentsperpagereduced'] = 'Reduced maximum students per page from {$a-
$string['subcategory'] = 'Normal category';
$string['submissions'] = 'Submissions';
$string['submittedon'] = 'Submitted: {$a}';
$string['sumofgradesupgradedgrades'] = 'A recent upgrade has changed the aggregation method "Sum of grades" to "Natural". Please review the grades in this course as it was previously using "Sum of grades".';
$string['sumofgradesupgradedgradeshidemessage'] = 'Got it';
$string['switchtofullview'] = 'Switch to full view';
$string['switchtosimpleview'] = 'Switch to simple view';
$string['tabs'] = 'Tabs';
@ -701,18 +716,31 @@ $string['useweighted'] = 'Use weighted';
$string['verbosescales'] = 'Verbose scales';
$string['viewbygroup'] = 'Group';
$string['viewgrades'] = 'View grades';
$string['warningexcludedsum'] = 'Warning: excluding of grades is not compatible with sum aggregation.';
$string['weight'] = 'weight';
$string['weight_help'] = 'A value used to determine the relative value of multiple grade items in a category or course.';
$string['weightcourse'] = 'Use weighted grades for course';
$string['weightedascending'] = 'Sort by weighted percent ascending';
$string['weighteddescending'] = 'Sort by weighted percent descending';
$string['weightoverride'] = 'weight adjustment';
$string['weightoverride_help'] = 'Uncheck this to reset a grade item weight to its automatically calculated value. Checking this will prevent the weight being automatically adjusted.';
$string['weightedpct'] = 'weighted %';
$string['weightedpctcontribution'] = 'weighted % contribution';
$string['weightofa'] = 'Weight of {$a}';
$string['weightorextracredit'] = 'Weight or extra credit';
$string['weight'] = 'Weight';
$string['weights'] = 'Weights';
$string['weightsadjusted'] = 'Your weights have been adjusted to total 100.';
$string['weightsedit'] = 'Edit weights and extra credits';
$string['weightuc'] = 'Weight';
$string['weightuc'] = 'Calculated weight';
$string['writinggradebookinfo'] = 'Writing gradebook settings';
$string['xml'] = 'XML';
$string['yes'] = 'Yes';
$string['yourgrade'] = 'Your grade';
// Deprecated since Moodle 2.8
$string['categoriesanditems'] = 'Setup';
$string['categoriesedit'] = 'Edit setup';
$string['edittree'] = 'Setup';
$string['fullview'] = 'Full view';
$string['simpleview'] = 'Simple view';

8
lib/db/install.xml Normal file → Executable file
View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20140918" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20140921" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -1717,6 +1717,7 @@
<FIELD NAME="multfactor" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="1.0" SEQUENCE="false" DECIMALS="5" COMMENT="Multiply all grades by this"/>
<FIELD NAME="plusfactor" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Add this to all grades"/>
<FIELD NAME="aggregationcoef" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Aggregation coefficient used for category weights or other aggregation types"/>
<FIELD NAME="aggregationcoef2" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Aggregation coefficient used for weights in aggregation types with both extra credit and weight"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Sorting order of the columns"/>
<FIELD NAME="display" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display as real grades, percentages (in reference to the minimum and maximum grades) or letters (A, B, C etc..), or course default (0)"/>
<FIELD NAME="decimals" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false" COMMENT="Also known as precision, the number of digits after the decimal point symbol."/>
@ -1724,6 +1725,7 @@
<FIELD NAME="locked" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 is locked, &amp;gt; 1 is a date to lock until (prevents update)"/>
<FIELD NAME="locktime" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="lock all final grades after this date"/>
<FIELD NAME="needsupdate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this flag is set, then the whole column will be recalculated"/>
<FIELD NAME="weightoverride" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The first time this grade_item was created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The last time this grade_item was modified"/>
</FIELDS>
@ -1764,6 +1766,8 @@
<FIELD NAME="informationformat" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="format of information text"/>
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="the time this grade was first created"/>
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="the time this grade was last modified"/>
<FIELD NAME="aggregationstatus" TYPE="char" LENGTH="10" NOTNULL="true" DEFAULT="unknown" SEQUENCE="false" COMMENT="One of several values describing how this grade_grade was used when calculating the aggregation. Possible values are &quot;unknown&quot;, &quot;dropped&quot;, &quot;novalue&quot;, &quot;used&quot;"/>
<FIELD NAME="aggregationweight" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="If the aggregationstatus == 'included', then this is the percent this item contributed to the aggregation."/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@ -1862,6 +1866,7 @@
<FIELD NAME="multfactor" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="1.0" SEQUENCE="false" DECIMALS="5" COMMENT="Multiply all grades by this"/>
<FIELD NAME="plusfactor" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Add this to all grades"/>
<FIELD NAME="aggregationcoef" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Aggregation coefficient used for category weights or other aggregation types"/>
<FIELD NAME="aggregationcoef2" TYPE="number" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" DECIMALS="5" COMMENT="Aggregation coefficient used for category weights or other aggregation types"/>
<FIELD NAME="sortorder" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Sorting order of the columns"/>
<FIELD NAME="hidden" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 is hidden, &amp;gt; 1 is a date to hide until (prevents viewing)"/>
<FIELD NAME="locked" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 is locked, &amp;gt; 1 is a date to lock until (prevents update)"/>
@ -1869,6 +1874,7 @@
<FIELD NAME="needsupdate" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If this flag is set, then the whole column will be recalculated"/>
<FIELD NAME="display" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="decimals" TYPE="int" LENGTH="1" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="weightoverride" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>

View File

@ -3837,5 +3837,92 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2014100100.00);
}
if ($oldversion < 2014100600.01) {
// Define field aggregationstatus to be added to grade_grades.
$table = new xmldb_table('grade_grades');
$field = new xmldb_field('aggregationstatus', XMLDB_TYPE_CHAR, '10', null, XMLDB_NOTNULL, null, 'unknown', 'timemodified');
// Conditionally launch add field aggregationstatus.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$field = new xmldb_field('aggregationweight', XMLDB_TYPE_NUMBER, '10, 5', null, null, null, null, 'aggregationstatus');
// Conditionally launch add field aggregationweight.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Define field aggregationcoef2 to be added to grade_items.
$table = new xmldb_table('grade_items');
$field = new xmldb_field('aggregationcoef2', XMLDB_TYPE_NUMBER, '10, 5', null, XMLDB_NOTNULL, null, '0', 'aggregationcoef');
// Conditionally launch add field aggregationcoef2.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$field = new xmldb_field('weightoverride', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'needsupdate');
// Conditionally launch add field weightoverride.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2014100600.01);
}
if ($oldversion < 2014100600.02) {
// Define field aggregationcoef2 to be added to grade_items_history.
$table = new xmldb_table('grade_items_history');
$field = new xmldb_field('aggregationcoef2', XMLDB_TYPE_NUMBER, '10, 5', null, XMLDB_NOTNULL, null, '0', 'aggregationcoef');
// Conditionally launch add field aggregationcoef2.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2014100600.02);
}
if ($oldversion < 2014100600.03) {
// Define field weightoverride to be added to grade_items_history.
$table = new xmldb_table('grade_items_history');
$field = new xmldb_field('weightoverride', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'decimals');
// Conditionally launch add field weightoverride.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2014100600.03);
}
if ($oldversion < 2014100600.04) {
// Set flags so we can display a notice on all courses that might
// be affected by the uprade to natural aggregation.
if (!get_config('grades_sumofgrades_upgrade_flagged', 'core')) {
// 13 == SUM_OF_GRADES.
$sql = 'SELECT DISTINCT courseid
FROM {grade_categories}
WHERE aggregation = ?';
$courses = $DB->get_records_sql($sql, array(13));
foreach ($courses as $course) {
set_config('show_sumofgrades_upgrade_' . $course->courseid, 1);
}
set_config('grades_sumofgrades_upgrade_flagged', 1);
}
// Main savepoint reached.
upgrade_main_savepoint(true, 2014100600.04);
}
return true;
}

View File

@ -68,7 +68,7 @@ define('GRADE_AGGREGATE_WEIGHTED_MEAN2', 11);
define('GRADE_AGGREGATE_EXTRACREDIT_MEAN', 12);
/**
* GRADE_AGGREGATE_WEIGHTED_MEAN2 - Use the sum of grades in the category for grade aggregation.
* GRADE_AGGREGATE_WEIGHTED_MEAN2 - Use Natural in the category for grade aggregation.
*/
define('GRADE_AGGREGATE_SUM', 13);

View File

@ -160,6 +160,12 @@ class grade_category extends grade_object {
*/
public $coefstring = null;
/**
* Static variable storing the result from {@link self::can_apply_limit_rules}.
* @var bool
*/
protected $canapplylimitrules;
/**
* Builds this category's path string based on its parents (if any) and its own id number.
* This is typically done just before inserting this object in the DB for the first time,
@ -417,6 +423,16 @@ class grade_category extends grade_object {
$grade_item->force_regrading();
}
/**
* Something that should be called before we start regrading the whole course.
*
* @return void
*/
public function pre_regrade_final_grades() {
$this->auto_update_weights();
$this->auto_update_max();
}
/**
* Generates and saves final grades in associated category grade item.
* These immediate children must already have their own final grades.
@ -458,9 +474,6 @@ class grade_category extends grade_object {
$items = $DB->get_records_sql($sql, $params);
}
// needed mostly for SUM agg type
$this->auto_update_max($items);
$grade_inst = new grade_grade();
$fields = 'g.'.implode(',g.', $grade_inst->required_fields);
@ -488,17 +501,29 @@ class grade_category extends grade_object {
$grade_values = array();
$excluded = array();
$oldgrade = null;
$grademaxoverrides = array();
$grademinoverrides = array();
foreach ($rs as $used) {
if ($used->userid != $prevuser) {
$this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);
$this->aggregate_grades($prevuser,
$items,
$grade_values,
$oldgrade,
$excluded,
$grademinoverrides,
$grademaxoverrides);
$prevuser = $used->userid;
$grade_values = array();
$excluded = array();
$oldgrade = null;
$grademaxoverrides = array();
$grademinoverrides = array();
}
$grade_values[$used->itemid] = $used->finalgrade;
$grademaxoverrides[$used->itemid] = $used->rawgrademax;
$grademinoverrides[$used->itemid] = $used->rawgrademin;
if ($used->excluded) {
$excluded[] = $used->itemid;
@ -508,7 +533,13 @@ class grade_category extends grade_object {
$oldgrade = $used;
}
}
$this->aggregate_grades($prevuser, $items, $grade_values, $oldgrade, $excluded);//the last one
$this->aggregate_grades($prevuser,
$items,
$grade_values,
$oldgrade,
$excluded,
$grademinoverrides,
$grademaxoverrides);//the last one
}
$rs->close();
@ -523,9 +554,24 @@ class grade_category extends grade_object {
* @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) {
global $CFG;
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;
@ -552,11 +598,29 @@ class grade_category extends grade_object {
// 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');
// 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;
$params['userid'] = $userid;
$sql = "SELECT itemid
FROM {grade_grades}
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
@ -566,44 +630,75 @@ class grade_category extends grade_object {
if (!is_null($oldfinalgrade)) {
$grade->update('aggregation');
}
$dropped = $grade_values;
$this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
return;
}
$minvisible = (bool) get_config('moodle', 'grade_report_showmin');
// normalize the grades first - all will have value 0...1
// ungraded items are not used in aggregation
// 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
// 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);
}
} else if (in_array($itemid, $excluded)) {
unset($grade_values[$itemid]);
continue;
}
// If grademin is hidden, set it to 0.
if (!$minvisible and $items[$itemid]->gradetype != GRADE_TYPE_SCALE) {
$items[$itemid]->grademin = 0;
}
$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)) {
// 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
$this->apply_limit_rules($grade_values, $items);
$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
@ -614,30 +709,126 @@ class grade_category extends grade_object {
if (!is_null($oldfinalgrade)) {
$grade->update('aggregation');
}
$this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
return;
}
// do the maths
$result = $this->aggregate_values_and_adjust_bounds($grade_values, $items);
$result = $this->aggregate_values_and_adjust_bounds($grade_values,
$items,
$usedweights,
$grademinoverrides,
$grademaxoverrides);
$agg_grade = $result['grade'];
if (!$minvisible and $this->grade_item->gradetype != GRADE_TYPE_SCALE) {
$this->grade_item->grademin = 0;
// 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
// 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);
// update in db if changed
if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
$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)) {
$grade->update('aggregation');
}
$this->set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit);
return;
}
/**
* Set the flags on the grade_grade items to indicate how individual grades are used
* in the aggregation.
*
* @param int $userid The user we have aggregated the grades for.
* @param array $usedweights An array with keys for each of the grade_item columns included in the aggregation. The value are the relative weight.
* @param array $novalue An array with keys for each of the grade_item columns skipped because
* they had no value in the aggregation.
* @param array $dropped An array with keys for each of the grade_item columns dropped
* because of any drop lowest/highest settings in the aggregation.
* @param array $extracredit An array with keys for each of the grade_item columns
* considered extra credit by the aggregation.
*/
private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit) {
global $DB;
// Included.
if (!empty($usedweights)) {
// The usedweights items are updated individually to record the weights.
foreach ($usedweights as $gradeitemid => $contribution) {
$DB->set_field_select('grade_grades',
'aggregationweight',
$contribution,
"itemid = :itemid AND userid = :userid",
array('itemid'=>$gradeitemid, 'userid'=>$userid));
}
// Now set the status flag for all these weights.
list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($usedweights), SQL_PARAMS_NAMED, 'g');
$itemlist['userid'] = $userid;
$DB->set_field_select('grade_grades',
'aggregationstatus',
'used',
"itemid $itemsql AND userid = :userid",
$itemlist);
}
// No value.
if (!empty($novalue)) {
list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($novalue), SQL_PARAMS_NAMED, 'g');
$itemlist['userid'] = $userid;
$DB->set_field_select('grade_grades',
'aggregationstatus',
'novalue',
"itemid $itemsql AND userid = :userid",
$itemlist);
}
// Dropped.
if (!empty($dropped)) {
list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($dropped), SQL_PARAMS_NAMED, 'g');
$itemlist['userid'] = $userid;
$DB->set_field_select('grade_grades',
'aggregationstatus',
'dropped',
"itemid $itemsql AND userid = :userid",
$itemlist);
}
// Extra credit.
if (!empty($extracredit)) {
list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($extracredit), SQL_PARAMS_NAMED, 'g');
$itemlist['userid'] = $userid;
$DB->set_field_select('grade_grades',
'aggregationstatus',
'extra',
"itemid $itemsql AND userid = :userid",
$itemlist);
}
}
/**
* Internal function that calculates the aggregated grade and new min/max for this grade category
*
@ -646,12 +837,24 @@ class grade_category extends grade_object {
* @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) {
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;
@ -662,23 +865,55 @@ class grade_category extends grade_object {
$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:
$agg_grade = array_pop($grade_values);
// 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, average used if multimode
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();
@ -690,6 +925,9 @@ class grade_category extends grade_object {
} else {
$converted_grade_values[$k] = $gv;
}
if ($weights !== null) {
$weights[$k] = 0;
}
}
$freq = array_count_values($converted_grade_values);
@ -698,6 +936,14 @@ class grade_category extends grade_object {
$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
@ -711,13 +957,22 @@ class grade_category extends grade_object {
}
$weightsum += $items[$itemid]->aggregationcoef;
$sum += $items[$itemid]->aggregationcoef * $grade_value;
if ($weights !== null) {
$weights[$itemid] = $items[$itemid]->aggregationcoef;
}
}
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;
@ -739,13 +994,23 @@ class grade_category extends grade_object {
}
$sum += $weight * $grade_value;
}
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 ($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
@ -757,9 +1022,22 @@ class grade_category extends grade_object {
if ($items[$itemid]->aggregationcoef == 0) {
$num += 1;
$sum += $grade_value;
if ($weights !== null) {
$weights[$itemid] = 1;
}
} else if ($items[$itemid]->aggregationcoef > 0) {
$sum += $items[$itemid]->aggregationcoef * $grade_value;
if ($weights !== null) {
$weights[$itemid] = 1;
}
}
}
if ($weights !== null && $num > 0) {
foreach ($grade_values as $itemid=>$grade_value) {
if ($weights[$itemid]) {
$weights[$itemid] = 1.0 / $num;
}
}
}
@ -773,17 +1051,86 @@ class grade_category extends grade_object {
case GRADE_AGGREGATE_SUM: // Add up all the items.
$num = count($grade_values);
// Excluded items can affect the grademax for this grade_item.
$sum = 0;
$sumweights = 0;
$grademin = 0;
$grademax = 0;
$sum = 0;
foreach ($grade_values as $itemid => $grade_value) {
$sum += $grade_value * ($items[$itemid]->grademax - $items[$itemid]->grademin);
$grademin += $items[$itemid]->grademin;
$grademax += $items[$itemid]->grademax;
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];
}
// Ignore extra credit and items with a weight of 0.
if ($items[$itemid]->aggregationcoef <= 0 && $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] = $items[$itemid]->aggregationcoef2 / $sumweights;
$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.
foreach ($grade_values as $itemid => $gradevalue) {
if (!$items[$itemid]->weightoverride) {
$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;
}
}
}
}
// We can use our freshly corrected weights below.
foreach ($grade_values as $itemid => $gradevalue) {
$sum += $gradevalue * $userweights[$itemid] * $grademax;
if ($weights !== null) {
$weights[$itemid] = $userweights[$itemid];
}
}
if ($grademax > 0) {
$agg_grade = $sum / $grademax; // Re-normalize score.
} else {
// Every item in the category is extra credit.
$agg_grade = $sum;
$grademax = $sum;
}
$agg_grade = $sum / ($grademax - $grademin);
break;
case GRADE_AGGREGATE_MEAN: // Arithmetic average of all grade items (if ungraded aggregated, NULL counted as minimum)
@ -791,6 +1138,12 @@ class grade_category extends grade_object {
$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;
}
@ -815,16 +1168,32 @@ class grade_category extends grade_object {
}
/**
* Some aggregation types may automatically update max grade
* Some aggregation types may need to update their max grade.
*
* @param array $items sub items
* This must be executed after updating the weights as it relies on them.
*
* @return void
*/
private function auto_update_max($items) {
private function auto_update_max() {
global $DB;
if ($this->aggregation != GRADE_AGGREGATE_SUM) {
// not needed at all
return;
}
// Find grade items of immediate children (category or grade items) and force site settings.
$this->load_grade_item();
$depends_on = $this->grade_item->depends_on();
$items = false;
if (!empty($depends_on)) {
list($usql, $params) = $DB->get_in_or_equal($depends_on);
$sql = "SELECT *
FROM {grade_items}
WHERE id $usql";
$items = $DB->get_records_sql($sql, $params);
}
if (!$items) {
if ($this->grade_item->grademax != 0 or $this->grade_item->gradetype != GRADE_TYPE_VALUE) {
@ -844,6 +1213,9 @@ class grade_category extends grade_object {
if ($item->aggregationcoef > 0) {
// extra credit from this activity - does not affect total
continue;
} else if ($item->aggregationcoef2 <= 0) {
// Items with a weight of 0 do not affect the total.
continue;
}
if ($item->gradetype == GRADE_TYPE_VALUE) {
@ -853,8 +1225,11 @@ class grade_category extends grade_object {
$maxes[$item->id] = $item->grademax; // 0 = nograde, 1 = first scale item, 2 = second scale item
}
}
// apply droplow and keephigh
$this->apply_limit_rules($maxes, $items);
if ($this->can_apply_limit_rules()) {
// Apply droplow and keephigh.
$this->apply_limit_rules($maxes, $items);
}
$max = array_sum($maxes);
// update db if anything changed
@ -867,52 +1242,134 @@ class grade_category extends grade_object {
}
/**
* Internal function for category grades summing
* Recalculate the weights of the grade items in this category.
*
* @param grade_grade $grade The grade item
* @param float $oldfinalgrade Old Final grade
* @param array $items Grade items
* @param array $grade_values Grade values
* @param array $excluded Excluded
* The category total is not updated here, a further call to
* {@link self::auto_update_max()} is required.
*
* @return void
*/
private function sum_grades(&$grade, $oldfinalgrade, $items, $grade_values, $excluded) {
if (empty($items)) {
return null;
private function auto_update_weights() {
if ($this->aggregation != GRADE_AGGREGATE_SUM) {
// This is only required if we are using natural weights.
return;
}
$children = $this->get_children();
// ungraded and excluded items are not used in aggregation
foreach ($grade_values as $itemid=>$v) {
$gradeitem = null;
if (is_null($v)) {
unset($grade_values[$itemid]);
// Calculate the sum of the grademax's of all the items within this category.
$totalnonoverriddengrademax = 0;
$totalgrademax = 0;
} else if (in_array($itemid, $excluded)) {
unset($grade_values[$itemid]);
// Out of 1, how much weight has been manually overriden by a user?
$totaloverriddenweight = 0;
$totaloverriddengrademax = 0;
// Has every assessment in this category been overridden?
$automaticgradeitemspresent = false;
// Does the grade item require normalising?
$requiresnormalising = false;
// This array keeps track of the id and weight of every grade item that has been overridden.
$overridearray = array();
foreach ($children as $sortorder => $child) {
$gradeitem = null;
if ($child['type'] == 'item') {
$gradeitem = $child['object'];
} else if ($child['type'] == 'category') {
$gradeitem = $child['object']->load_grade_item();
}
// Record the ID and the weight for this grade item.
$overridearray[$gradeitem->id] = array();
$overridearray[$gradeitem->id]['extracredit'] = intval($gradeitem->aggregationcoef);
$overridearray[$gradeitem->id]['weight'] = $gradeitem->aggregationcoef2;
$overridearray[$gradeitem->id]['weightoverride'] = intval($gradeitem->weightoverride);
// If this item has had its weight overridden then set the flag to true, but
// only if all previous items were also overridden. Note that extra credit items
// are counted as overridden grade items.
if (!$gradeitem->weightoverride && $gradeitem->aggregationcoef == 0) {
$automaticgradeitemspresent = true;
}
if ($gradeitem->aggregationcoef > 0) {
// An extra credit grade item doesn't contribute to $totaloverriddengrademax.
continue;
} else if ($gradeitem->weightoverride > 0 && $gradeitem->aggregationcoef2 <= 0) {
// An overriden item that defines a weight of 0 does not contribute to $totaloverriddengrademax.
continue;
}
$totalgrademax += $gradeitem->grademax;
if ($gradeitem->weightoverride > 0) {
$totaloverriddenweight += $gradeitem->aggregationcoef2;
$totaloverriddengrademax += $gradeitem->grademax;
}
}
// use 0 if grade missing, droplow used and aggregating all items
if (!$this->aggregateonlygraded and !empty($this->droplow)) {
// Initialise this variable (used to keep track of the weight override total).
$normalisetotal = 0;
// Keep a record of how much the override total is to see if it is above 100. It it is then we need to set the
// other weights to zero and normalise the others.
$overriddentotal = 0;
// If the overridden weight total is higher than 1 then set the other untouched weights to zero.
$setotherweightstozero = false;
// Total up all of the weights.
foreach ($overridearray as $gradeitemdetail) {
// If the grade item has extra credit, then don't add it to the normalisetotal.
if (!$gradeitemdetail['extracredit']) {
$normalisetotal += $gradeitemdetail['weight'];
}
if ($gradeitemdetail['weightoverride'] && !$gradeitemdetail['extracredit']) {
// Add overriden weights up to see if they are greater than 1.
$overriddentotal += $gradeitemdetail['weight'];
}
}
if ($overriddentotal > 1) {
// Make sure that this catergory of weights gets normalised.
$requiresnormalising = true;
// The normalised weights are only the overridden weights, so we just use the total of those.
$normalisetotal = $overriddentotal;
}
foreach ($items as $itemid=>$value) {
$totalnonoverriddengrademax = $totalgrademax - $totaloverriddengrademax;
if (!isset($grade_values[$itemid]) and !in_array($itemid, $excluded)) {
$grade_values[$itemid] = 0;
reset($children);
foreach ($children as $sortorder => $child) {
$gradeitem = null;
if ($child['type'] == 'item') {
$gradeitem = $child['object'];
} else if ($child['type'] == 'category') {
$gradeitem = $child['object']->load_grade_item();
}
if (!$gradeitem->weightoverride) {
// Calculations with a grade maximum of zero will cause problems. Just set the weight to zero.
if ($totaloverriddenweight >= 1 || $totalnonoverriddengrademax == 0 || $gradeitem->grademax == 0) {
// There is no more weight to distribute.
$gradeitem->aggregationcoef2 = 0;
} else {
// Calculate this item's weight as a percentage of the non-overridden total grade maxes
// then convert it to a proportion of the available non-overriden weight.
$gradeitem->aggregationcoef2 = ($gradeitem->grademax/$totalnonoverriddengrademax) *
(1 - $totaloverriddenweight);
}
$gradeitem->update();
} else if ((!$automaticgradeitemspresent && $normalisetotal != 1) || ($requiresnormalising)) {
// Just divide the overriden weight for this item against the total weight override of all
// items in this category.
if ($normalisetotal == 0) {
$gradeitem->aggregationcoef2 = 0;
} else {
$gradeitem->aggregationcoef2 = $overridearray[$gradeitem->id]['weight'] / $normalisetotal;
}
// Update the grade item to reflect these changes.
$gradeitem->update();
}
}
$this->apply_limit_rules($grade_values, $items);
$sum = array_sum($grade_values);
$grade->finalgrade = $this->grade_item->bounded_grade($sum);
// update in db if changed
if (grade_floats_different($grade->finalgrade, $oldfinalgrade)) {
$grade->update('aggregation');
}
return;
}
/**
@ -1013,6 +1470,70 @@ class grade_category extends grade_object {
}
}
/**
* Returns whether or not we can apply the limit rules.
*
* There are cases where drop lowest or keep highest should not be used
* at all. This method will determine whether or not this logic can be
* applied considering the current setup of the category.
*
* @return bool
*/
public function can_apply_limit_rules() {
if ($this->canapplylimitrules !== null) {
return $this->canapplylimitrules;
}
// Set it to be supported by default.
$this->canapplylimitrules = true;
// Natural aggregation.
if ($this->aggregation == GRADE_AGGREGATE_SUM) {
$canapply = true;
// Check until one child breaks the rules.
$gradeitems = $this->get_children();
$validitems = 0;
$lastweight = null;
$lastmaxgrade = null;
foreach ($gradeitems as $gradeitem) {
$gi = $gradeitem['object'];
if ($gradeitem['type'] == 'category') {
// Sub categories are not allowed because they can have dynamic weights/maxgrades.
$canapply = false;
break;
}
if ($gi->aggregationcoef > 0) {
// Extra credit items are not allowed.
$canapply = false;
break;
}
if ($lastweight !== null && $lastweight != $gi->aggregationcoef2) {
// One of the weight differs from another item.
$canapply = false;
break;
}
if ($lastmaxgrade !== null && $lastmaxgrade != $gi->grademax) {
// One of the max grade differ from another item. This is not allowed for now
// because we could be end up with different max grade between users for this category.
$canapply = false;
break;
}
$lastweight = $gi->aggregationcoef2;
$lastmaxgrade = $gi->grademax;
}
$this->canapplylimitrules = $canapply;
}
return $this->canapplylimitrules;
}
/**
* Returns true if category uses extra credit of any kind
*
@ -1109,7 +1630,7 @@ class grade_category extends grade_object {
$this->coefstring = 'aggregationcoefextraweight';
} else if ($this->aggregation == GRADE_AGGREGATE_SUM) {
$this->coefstring = 'aggregationcoefextrasum';
$this->coefstring = 'aggregationcoefextraweightsum';
} else {
$this->coefstring = 'aggregationcoef';
@ -1437,6 +1958,31 @@ class grade_category extends grade_object {
}
}
/**
* Describe the aggregation settings for this category so the reports make more sense.
*
* @return string description
*/
public function get_description() {
$allhelp = array();
$aggrstrings = grade_helper::get_aggregation_strings();
$allhelp[] = $aggrstrings[$this->aggregation];
if ($this->droplow && $this->can_apply_limit_rules()) {
$allhelp[] = get_string('droplowestvalues', 'grades', $this->droplow);
}
if ($this->keephigh && $this->can_apply_limit_rules()) {
$allhelp[] = get_string('keephighestvalues', 'grades', $this->keephigh);
}
if (!$this->aggregateonlygraded) {
$allhelp[] = get_string('aggregatenotonlygraded', 'grades');
}
if ($this->aggregatesubcats) {
$allhelp[] = get_string('aggregatesubcatsshort', 'grades');
}
return implode('. ', $allhelp) . '.';
}
/**
* Sets this category's parent id
*

View File

@ -49,7 +49,8 @@ class grade_grade extends grade_object {
*/
public $required_fields = array('id', 'itemid', 'userid', 'rawgrade', 'rawgrademax', 'rawgrademin',
'rawscaleid', 'usermodified', 'finalgrade', 'hidden', 'locked',
'locktime', 'exported', 'overridden', 'excluded', 'timecreated', 'timemodified');
'locktime', 'exported', 'overridden', 'excluded', 'timecreated',
'timemodified', 'aggregationstatus', 'aggregationweight');
/**
* Array of optional fields with default values (these should match db defaults)
@ -159,6 +160,17 @@ class grade_grade extends grade_object {
*/
public $timemodified = null;
/**
* Aggregation status flag. Can be one of 'unknown', 'dropped', 'novalue' or 'used'.
* @var string $aggregationstatus
*/
public $aggregationstatus = 'unknown';
/**
* Aggregation weight is the specific weight used in the aggregation calculation for this grade.
* @var float $aggregationweight
*/
public $aggregationweight = null;
/**
* Returns array of grades for given grade_item+users
@ -285,6 +297,46 @@ class grade_grade extends grade_object {
return $this->timecreated;
}
/**
* Returns the weight this grade contributed to the aggregated grade
*
* @return float|null
*/
public function get_aggregationweight() {
return $this->aggregationweight;
}
/**
* Set aggregationweight.
*
* @param float $aggregationweight
* @return void
*/
public function set_aggregationweight($aggregationweight) {
$this->aggregationweight = $aggregationweight;
$this->update();
}
/**
* Returns the info on how this value was used in the aggregated grade
*
* @return string One of 'dropped', 'excluded', 'novalue', 'used' or 'extra'
*/
public function get_aggregationstatus() {
return $this->aggregationstatus;
}
/**
* Set aggregationstatus flag
*
* @param string $aggregationstatus
* @return void
*/
public function set_aggregationstatus($aggregationstatus) {
$this->aggregationstatus = $aggregationstatus;
$this->update();
}
/**
* Returns timestamp when last graded, null if no grade present
*
@ -620,6 +672,9 @@ class grade_grade extends grade_object {
* 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;
@ -634,6 +689,8 @@ class grade_grade extends grade_object {
$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;
@ -663,8 +720,12 @@ class grade_grade extends grade_object {
return array('unknown' => array(),
'altered' => array(),
'alteredgrademax' => array(),
'alteredgrademin' => 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);
@ -724,6 +785,8 @@ class grade_grade extends grade_object {
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.
@ -742,6 +805,8 @@ class grade_grade extends grade_object {
foreach ($values as $itemid=>$value) {
if (is_null($value)) {
unset($values[$itemid]);
$alteredaggregationstatus[$itemid] = 'novalue';
$alteredaggregationweight[$itemid] = null;
}
}
} else {
@ -753,7 +818,21 @@ class grade_grade extends grade_object {
}
// 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
@ -765,7 +844,8 @@ class grade_grade extends grade_object {
continue;
}
$adjustedgrade = $grade_category->aggregate_values_and_adjust_bounds($values, $grade_items);
$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'],
@ -774,6 +854,13 @@ class grade_grade extends grade_object {
$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'];
@ -798,7 +885,9 @@ class grade_grade extends grade_object {
return array('unknown' => $unknown,
'altered' => $altered,
'alteredgrademax' => $alteredgrademax,
'alteredgrademin' => $alteredgrademin);
'alteredgrademin' => $alteredgrademin,
'alteredaggregationstatus' => $alteredaggregationstatus,
'alteredaggregationweight' => $alteredaggregationweight);
}
/**
@ -923,5 +1012,17 @@ class grade_grade extends grade_object {
// Pass information on to completion system
$completion->inform_grade_changed($cm, $this->grade_item, $this, $deleted);
}
}
/**
* Get some useful information about how this grade_grade is reflected in the aggregation
* for the grade_category. For example this could be an extra credit item, and it could be
* dropped because it's in the X lowest or highest.
*
* @return array(status, weight) - A keyword and a numerical weight that represents how this grade was included in the aggregation.
*/
function get_aggregation_hint() {
return array('status' => $this->get_aggregationstatus(),
'weight' => $this->get_aggregationweight());
}
}

View File

@ -50,8 +50,8 @@ class grade_item extends grade_object {
public $required_fields = array('id', 'courseid', 'categoryid', 'itemname', 'itemtype', 'itemmodule', 'iteminstance',
'itemnumber', 'iteminfo', 'idnumber', 'calculation', 'gradetype', 'grademax', 'grademin',
'scaleid', 'outcomeid', 'gradepass', 'multfactor', 'plusfactor', 'aggregationcoef',
'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime', 'needsupdate', 'timecreated',
'timemodified');
'aggregationcoef2', 'sortorder', 'display', 'decimals', 'hidden', 'locked', 'locktime',
'needsupdate', 'weightoverride', 'timecreated', 'timemodified');
/**
* The course this grade_item belongs to.
@ -199,11 +199,17 @@ class grade_item extends grade_object {
public $plusfactor = 0;
/**
* Aggregation coeficient used for weighted averages
* Aggregation coeficient used for weighted averages or extra credit
* @var float $aggregationcoef
*/
public $aggregationcoef = 0;
/**
* Aggregation coeficient used for weighted averages only
* @var float $aggregationcoef2
*/
public $aggregationcoef2 = 0;
/**
* Sorting order of the columns.
* @var int $sortorder
@ -240,6 +246,11 @@ class grade_item extends grade_object {
*/
public $needsupdate = 1;
/**
* If set, the grade item's weight has been overridden by a user and should not be automatically adjusted.
*/
public $weightoverride = 0;
/**
* Cached dependson array
* @var array An array of cached grade item dependencies.
@ -277,6 +288,7 @@ class grade_item extends grade_object {
$this->multfactor = grade_floatval($this->multfactor);
$this->plusfactor = grade_floatval($this->plusfactor);
$this->aggregationcoef = grade_floatval($this->aggregationcoef);
$this->aggregationcoef2 = grade_floatval($this->aggregationcoef2);
return parent::update($source);
}
@ -306,13 +318,15 @@ class grade_item extends grade_object {
$multfactordiff = grade_floats_different($db_item->multfactor, $this->multfactor);
$plusfactordiff = grade_floats_different($db_item->plusfactor, $this->plusfactor);
$acoefdiff = grade_floats_different($db_item->aggregationcoef, $this->aggregationcoef);
$acoefdiff2 = grade_floats_different($db_item->aggregationcoef2, $this->aggregationcoef2);
$weightoverride = grade_floats_different($db_item->weightoverride, $this->weightoverride);
$needsupdatediff = !$db_item->needsupdate && $this->needsupdate; // force regrading only if setting the flag first time
$lockeddiff = !empty($db_item->locked) && empty($this->locked); // force regrading only when unlocking
return ($calculationdiff || $categorydiff || $gradetypediff || $grademaxdiff || $grademindiff || $scaleiddiff
|| $outcomeiddiff || $multfactordiff || $plusfactordiff || $needsupdatediff
|| $lockeddiff || $acoefdiff || $locktimediff);
|| $lockeddiff || $acoefdiff || $acoefdiff2 || $weightoverride || $locktimediff);
}
/**
@ -1282,6 +1296,19 @@ class grade_item extends grade_object {
}
}
/**
* A grade item can return a more detailed description which will be added to the header of the column/row in some reports.
*
* @return string description
*/
public function get_description() {
if ($this->is_course_item() || $this->is_category_item()) {
$categoryitem = $this->load_item_category();
return $categoryitem->get_description();
}
return '';
}
/**
* Sets this item's categoryid. A generic method shared by objects that have a parent id of some kind.
*

View File

@ -1044,6 +1044,25 @@ function grade_regrade_final_grades($courseid, $userid=null, $updated_item=null)
}
}
// Categories might have to run some processing before we fetch the grade items.
// This gives them a final opportunity to update and mark their children to be updated.
// We need to work on the children categories up to the parent ones, so that, for instance,
// if a category total is updated it will be reflected in the parent category.
$cats = grade_category::fetch_all(array('courseid' => $courseid));
$flatcattree = array();
foreach ($cats as $cat) {
if (!isset($flatcattree[$cat->depth])) {
$flatcattree[$cat->depth] = array();
}
$flatcattree[$cat->depth][] = $cat;
}
krsort($flatcattree);
foreach ($flatcattree as $depth => $cats) {
foreach ($cats as $cat) {
$cat->pre_regrade_final_grades();
}
}
$grade_items = grade_item::fetch_all(array('courseid'=>$courseid));
$depends_on = array();

View File

@ -992,7 +992,7 @@ class behat_general extends behat_base {
* @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should contain "(?P<value_string>[^"]*)"$/
* @throws ElementNotFoundException
* @param string $row row text which will be looked in.
* @param string $column column text to search
* @param string $column column text to search (or numeric value for the column position)
* @param string $table table id/class/caption
* @param string $value text to check.
*/
@ -1004,42 +1004,41 @@ class behat_general extends behat_base {
$valueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($value);
$columnliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($column);
// Header can be in thead or tbody (first row), following xpath should work.
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) {
// Column indicated as a number, just use it as position of the column.
$columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]";
} else {
// Header can be in thead or tbody (first row), following xpath should work.
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
$columnliteral . "])]";
// Check if column exists.
$columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]";
$columnheader = $this->getSession()->getDriver()->find($columnheaderxpath);
if (empty($columnheader)) {
$columnexceptionmsg = $column . '" in table "' . $table . '"';
throw new ElementNotFoundException($this->getSession(), 'Column', null, $columnexceptionmsg);
// Check if column exists.
$columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]";
$columnheader = $this->getSession()->getDriver()->find($columnheaderxpath);
if (empty($columnheader)) {
$columnexceptionmsg = $column . '" in table "' . $table . '"';
throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column', null, $columnexceptionmsg);
}
// Following conditions were considered before finding column count.
// 1. Table header can be in thead/tr/th or tbody/tr/td[1].
// 2. First column can have th (Gradebook -> user report), so having lenient sibling check.
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath .
"/preceding-sibling::*) + 1]";
}
// Check if value exists in specific row/column.
// Get row xpath.
$rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)=" . $rowliteral . "] | td[normalize-space(.)=" . $rowliteral . "]]";
// Following conditions were considered before finding column count.
// 1. Table header can be in thead/tr/th or tbody/tr/td[1].
// 2. First column can have th (Gradebook -> user report), so having lenient sibling check.
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath .
"/preceding-sibling::*) + 1]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
// Looks for the requested node inside the container node.
$coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath);
if (empty($coumnnode)) {
// Check if tbody/tr[1] contains header selector.
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $tbodyheaderxpath .
"/preceding-sibling::*) + 1]";
$columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
$coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath);
if (empty($coumnnode)) {
$locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column;
throw new ElementNotFoundException($this->getSession(), 'Column value', null, $locatorexceptionmsg);
}
$locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column;
throw new ElementNotFoundException($this->getSession(), "\n$columnvaluexpath\n\n".'Column value', null, $locatorexceptionmsg);
}
}
@ -1071,6 +1070,10 @@ class behat_general extends behat_base {
* Checks that the provided value exist in table.
* More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
*
* First row may contain column headers or numeric indexes of the columns
* (syntax -1- is also considered to be column index). Column indexes are
* useful in case of multirow headers and/or presence of cells with colspan.
*
* @Then /^the following should exist in the "(?P<table_string>[^"]*)" table:$/
* @throws ExpectationException
* @param string $table name of table
@ -1081,10 +1084,14 @@ class behat_general extends behat_base {
public function following_should_exist_in_the_table($table, TableNode $data) {
$datahash = $data->getHash();
foreach ($datahash as $value) {
$row = array_shift($value);
foreach ($value as $column => $value) {
$this->row_column_of_table_should_contain($row, $column, $table, $value);
foreach ($datahash as $row) {
$firstcell = null;
foreach ($row as $column => $value) {
if ($firstcell === null) {
$firstcell = $value;
} else {
$this->row_column_of_table_should_contain($firstcell, $column, $table, $value);
}
}
}
}

View File

@ -16,6 +16,9 @@ information provided here is intended especially for developers.
* Scheduled tasks have gained support for syntax to introduce variability when a
task will run across installs. When a when hour or minute are defined as 'R'
they will be installed with a random hour/minute value.
* Several classes grade_edit_tree_column_xxx were removed since grades setup page
has been significantly changed. These classes should not be used outside of
gradebook or developers can copy them into their plugins from 2.7 branch.
DEPRECATIONS:
* completion_info->get_incomplete_criteria() is deprecated and will be removed in Moodle 3.0.

View File

@ -49,12 +49,12 @@ Feature: Set a quiz to be marked complete when the student uses all attempts all
| Feedback for the response 'False'. | So you think it is false |
And I follow "Course 1"
And I follow "Grades"
And I follow "Simple view"
And I navigate to "Set up grades layout" node in "Grade administration > Settings"
And I follow "Edit quiz Test quiz name"
Then I should see "Edit grade item"
And I set the field "gradepass" to "5"
And I press "Save changes"
And I should see "Simple view"
And I should see "Set up grades layout"
Then I log out
And I log in as "student1"

View File

@ -48,13 +48,13 @@ Feature: Set a quiz to be marked complete when the student passes
| Feedback for the response 'False'. | So you think it is false |
And I follow "Course 1"
And I follow "Grades"
And I set the field "jump" to "Simple view"
And I set the field "jump" to "Set up grades layout"
And I press "Go"
And I follow "Edit quiz Test quiz name"
Then I should see "Edit grade item"
And I set the field "gradepass" to "5"
And I press "Save changes"
Then I should see "Simple view"
Then I should see "Set up grades layout"
And I log out
And I log in as "student1"

BIN
pix/t/reset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

15
pix/t/reset.svg Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">
]>
<svg version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"
x="0px" y="0px" width="12px" height="12px" viewBox="-0.1 0 12 12" style="overflow:visible;enable-background:new -0.1 0 12 12;"
xml:space="preserve">
<defs>
</defs>
<path style="fill:#989898;" d="M5.9,0C3,0,0.5,2.3,0.2,5.1h2.3c0.4-1.7,1.9-3,3.6-3c2.1,0,3.7,1.7,3.7,3.7S8.2,9.6,6.1,9.6
c-0.8,0-1.6-0.3-2.3-0.8h0.8c0.5,0,0.8-0.3,0.8-0.8V7.3C5.2,6.9,4.9,6.6,4.5,6.6h-3H0.8C0.3,6.6,0,6.9,0,7.3v0.8v3
c0,0.5,0.3,0.8,0.8,0.8h0.8c0.5,0,0.8-0.3,0.8-0.8v-0.7c1.1,0.8,2.3,1.4,3.7,1.4c3.2,0,5.8-2.6,5.8-5.8C11.8,2.6,9,0,5.9,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

View File

@ -72,3 +72,7 @@
#page-grade-edit-outcome-course .courseoutcomes td {
text-align:center;
}
.path-grade-edit-tree table.setup-grades .column-select {
text-align: center;
}

View File

@ -230,3 +230,7 @@
}
}
}
.path-grade-edit-tree table.setup-grades .column-select {
text-align: center;
}

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2014100200.01; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2014100600.04; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.