diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index 78de7b50695..83ac856a34c 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -486,6 +486,11 @@ class restore_gradebook_structure_step extends restore_structure_step { require_once($CFG->libdir . '/db/upgradelib.php'); upgrade_extra_credit_weightoverride($this->get_courseid()); } + // Calculated grade items need recalculating for backups made between 2.8 release (20141110) and the fix release (20150627). + if (!$gradebookcalculationsfreeze && $backupbuild >= 20141110 && $backupbuild < 20150627) { + require_once($CFG->libdir . '/db/upgradelib.php'); + upgrade_calculated_grade_items($this->get_courseid()); + } } } diff --git a/grade/edit/tree/category.php b/grade/edit/tree/category.php index 206b12de92d..9695c9c747d 100644 --- a/grade/edit/tree/category.php +++ b/grade/edit/tree/category.php @@ -86,15 +86,31 @@ if ($id) { } else { $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; + // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they + // wish to update the grades. + $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); + // Stick with the original code if the grade book is frozen. + if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { + 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 { + if ($category->aggregation == GRADE_AGGREGATE_SUM && !$grade_item->is_calculated()) { + // 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. + // This does not apply to calculated category totals. + $category->grademax = 100; + $category->grade_item_grademax = 100; + $category->grademin = 0; + $category->grade_item_grademin = 0; + } } } else { diff --git a/lib/db/install.php b/lib/db/install.php index 04538bfb106..d3ca874c629 100644 --- a/lib/db/install.php +++ b/lib/db/install.php @@ -132,6 +132,7 @@ function xmldb_main_install() { 'texteditors' => 'atto,tinymce,textarea', 'upgrade_minmaxgradestepignored' => 1, // New installs should not run this upgrade step. 'upgrade_extracreditweightsstepignored' => 1, // New installs should not run this upgrade step. + 'upgrade_calculatedgradeitemsignored' => 1, // New installs should not run this upgrade step. ); foreach($defaults as $key => $value) { set_config($key, $value); diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index 7cfefb46ee9..434a9eeeb02 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -4418,5 +4418,23 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2015051100.08); } + if ($oldversion < 2015051100.10) { + // MDL-48239. Changed calculated grade items so that the maximum and minimum grade can be set. + + // If the changes are accepted and a regrade is done on the gradebook then some grades may change significantly. + // This is here to freeze the gradebook in affected courses. + + // This script is included in each major version upgrade process so make sure we don't run it twice. + if (empty($CFG->upgrade_calculatedgradeitemsignored)) { + upgrade_calculated_grade_items(); + + // To skip running the same script on the upgrade to the next major release. + set_config('upgrade_calculatedgradeitemsignored', 1); + } + + // Main savepoint reached. + upgrade_main_savepoint(true, 2015051100.10); + } + return true; } diff --git a/lib/db/upgradelib.php b/lib/db/upgradelib.php index 23af19e1486..b43d4a73319 100644 --- a/lib/db/upgradelib.php +++ b/lib/db/upgradelib.php @@ -564,4 +564,100 @@ function upgrade_extra_credit_weightoverride($onlycourseid = 0) { set_config('gradebook_calculations_freeze_' . $courseid, 20150619); } } +} + +/** + * Marks all courses that require calculated grade items be updated. + * + * Used during upgrade and in course restore process. + * + * This upgrade script is needed because the calculated grade items were stuck with a maximum of 100 and could be changed. + * This flags the courses that are affected and the grade book is frozen to retain grade integrity. + * + * @param int $courseid Specify a course ID to run this script on just one course. + */ +function upgrade_calculated_grade_items($courseid = null) { + global $DB, $CFG; + + $affectedcourses = array(); + $possiblecourseids = array(); + $params = array(); + $singlecoursesql = ''; + if (isset($courseid)) { + $singlecoursesql = "AND ns.id = :courseid"; + $params['courseid'] = $courseid; + } + $siteminmaxtouse = $CFG->grade_minmaxtouse; + $courseidsql = "SELECT ns.id + FROM ( + SELECT c.id, coalesce(gs.value, :siteminmax) AS gradevalue + FROM {course} c + LEFT JOIN {grade_settings} gs + ON c.id = gs.courseid + AND ((gs.name = 'minmaxtouse' AND gs.value = '2')) + ) ns + WHERE ns.gradevalue = '2' $singlecoursesql"; + $params['siteminmax'] = $siteminmaxtouse; + $courses = $DB->get_records_sql($courseidsql, $params); + foreach ($courses as $course) { + $possiblecourseids[$course->id] = $course->id; + } + + if (!empty($possiblecourseids)) { + list($sql, $params) = $DB->get_in_or_equal($possiblecourseids); + // A calculated grade item grade min != 0 and grade max != 100 and the course setting is set to + // "Initial min and max grades". + $coursesql = "SELECT DISTINCT courseid + FROM {grade_items} + WHERE calculation IS NOT NULL + AND itemtype = 'manual' + AND (grademax <> 100 OR grademin <> 0) + AND courseid $sql"; + $affectedcourses = $DB->get_records_sql($coursesql, $params); + } + + // Check for second type of affected courses. + // If we already have the courseid parameter set in the affectedcourses then there is no need to run through this section. + if (!isset($courseid) || !in_array($courseid, $affectedcourses)) { + $singlecoursesql = ''; + $params = array(); + if (isset($courseid)) { + $singlecoursesql = "AND courseid = :courseid"; + $params['courseid'] = $courseid; + } + $nestedsql = "SELECT id + FROM {grade_items} + WHERE itemtype = 'category' + AND calculation IS NOT NULL $singlecoursesql"; + $calculatedgradecategories = $DB->get_records_sql($nestedsql, $params); + $categoryids = array(); + foreach ($calculatedgradecategories as $key => $gradecategory) { + $categoryids[$key] = $gradecategory->id; + } + + if (!empty($categoryids)) { + list($sql, $params) = $DB->get_in_or_equal($categoryids); + // A category with a calculation where the raw grade min and the raw grade max don't match the grade min and grade max + // for the category. + $coursesql = "SELECT DISTINCT gi.courseid + FROM {grade_grades} gg, {grade_items} gi + WHERE gi.id = gg.itemid + AND (gg.rawgrademax <> gi.grademax OR gg.rawgrademin <> gi.grademin) + AND gi.id $sql"; + $additionalcourses = $DB->get_records_sql($coursesql, $params); + foreach ($additionalcourses as $key => $additionalcourse) { + if (!array_key_exists($key, $affectedcourses)) { + $affectedcourses[$key] = $additionalcourse; + } + } + } + } + + foreach ($affectedcourses as $courseid) { + // Check to see if the gradebook freeze is already in affect. + $gradebookfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid->courseid); + if (!$gradebookfreeze) { + set_config('gradebook_calculations_freeze_' . $courseid->courseid, 20150627); + } + } } \ No newline at end of file diff --git a/lib/grade/grade_category.php b/lib/grade/grade_category.php index 0fde1008a63..fc80f248313 100644 --- a/lib/grade/grade_category.php +++ b/lib/grade/grade_category.php @@ -1375,6 +1375,19 @@ class grade_category extends grade_object { $this->load_grade_item(); $depends_on = $this->grade_item->depends_on(); + // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they + // wish to update the grades. + $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->courseid); + // Only run if the gradebook isn't frozen. + if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { + // Do nothing. + } else{ + // Don't automatically update the max for calculated items. + if ($this->grade_item->is_calculated()) { + return; + } + } + $items = false; if (!empty($depends_on)) { list($usql, $params) = $DB->get_in_or_equal($depends_on); diff --git a/lib/grade/grade_grade.php b/lib/grade/grade_grade.php index 0ce19f9d27d..5de62572abb 100644 --- a/lib/grade/grade_grade.php +++ b/lib/grade/grade_grade.php @@ -350,11 +350,25 @@ class grade_grade extends grade_object { // When the following setting is turned on we use the grade_grade raw min and max values. $minmaxtouse = grade_get_setting($this->grade_item->courseid, 'minmaxtouse', $CFG->grade_minmaxtouse); - // Only aggregate items use separate min grades. - if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE || $this->grade_item->is_aggregate_item()) { - return array($this->rawgrademin, $this->rawgrademax); + // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they + // wish to update the grades. + $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->grade_item->courseid); + // Gradebook is frozen, run through old code. + if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { + // Only aggregate items use separate min grades. + if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE || $this->grade_item->is_aggregate_item()) { + return array($this->rawgrademin, $this->rawgrademax); + } else { + return array($this->grade_item->grademin, $this->grade_item->grademax); + } } else { - return array($this->grade_item->grademin, $this->grade_item->grademax); + // Only aggregate items use separate min grades, unless they are calculated grade items. + if (($this->grade_item->is_aggregate_item() && !$this->grade_item->is_calculated()) + || $minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE) { + return array($this->rawgrademin, $this->rawgrademax); + } else { + return array($this->grade_item->grademin, $this->grade_item->grademax); + } } } diff --git a/lib/grade/grade_item.php b/lib/grade/grade_item.php index 4b7dc15c0a5..47289b2d2d6 100644 --- a/lib/grade/grade_item.php +++ b/lib/grade/grade_item.php @@ -1951,8 +1951,25 @@ class grade_item extends grade_object { // can not use own final grade during calculation unset($params['gi'.$this->id]); + // Check to see if the gradebook is frozen. This allows grades to not be altered at all until a user verifies that they + // wish to update the grades. + $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->courseid); + + $rawminandmaxchanged = false; // insert final grade - will be needed later anyway if ($oldgrade) { + // Only run through this code if the gradebook isn't frozen. + if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { + // Do nothing. + } else { + // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the + // grade_item grade maximum and minimum respectively. + if ($oldgrade->rawgrademax != $this->grademax || $oldgrade->rawgrademin != $this->grademin) { + $rawminandmaxchanged = true; + $oldgrade->rawgrademax = $this->grademax; + $oldgrade->rawgrademin = $this->grademin; + } + } $oldfinalgrade = $oldgrade->finalgrade; $grade = new grade_grade($oldgrade, false); // fetching from db is not needed $grade->grade_item =& $this; @@ -1960,6 +1977,16 @@ class grade_item extends grade_object { } else { $grade = new grade_grade(array('itemid'=>$this->id, 'userid'=>$userid), false); $grade->grade_item =& $this; + $rawminandmaxchanged = false; + if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { + // Do nothing. + } else { + // The grade_grade for a calculated item should have the raw grade maximum and minimum set to the + // grade_item grade maximum and minimum respectively. + $rawminandmaxchanged = true; + $grade->rawgrademax = $this->grademax; + $grade->rawgrademin = $this->grademin; + } $grade->insert('system'); $oldfinalgrade = null; } diff --git a/version.php b/version.php index 907a2e322a0..600c1ae394b 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2015051100.09; // 20150511 = branching date YYYYMMDD - do not modify! +$version = 2015051100.10; // 20150511 = branching date YYYYMMDD - do not modify! // RR = release increments - 00 in DEV branches. // .XX = incremental changes.