MDL-47110 core_grades: Normalisation of grade weights.
[moodle.git] / grade / edit / tree / index.php
blob6e4f9145711fabe643ddd3562e81b6c4b851c1e2
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Edit and review page for grade categories and items
20 * @package core_grades
21 * @copyright 2008 Nicolas Connault
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 require_once '../../../config.php';
26 require_once $CFG->dirroot.'/grade/lib.php';
27 require_once $CFG->dirroot.'/grade/report/lib.php'; // for preferences
28 require_once $CFG->dirroot.'/grade/edit/tree/lib.php';
30 $courseid = required_param('id', PARAM_INT);
31 $action = optional_param('action', 0, PARAM_ALPHA);
32 $eid = optional_param('eid', 0, PARAM_ALPHANUM);
33 $category = optional_param('category', null, PARAM_INT);
34 $aggregationtype = optional_param('aggregationtype', null, PARAM_INT);
36 $url = new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid));
37 $PAGE->set_url($url);
38 $PAGE->set_pagelayout('admin');
40 /// Make sure they can even access this course
41 if (!$course = $DB->get_record('course', array('id' => $courseid))) {
42 print_error('nocourseid');
45 require_login($course);
46 $context = context_course::instance($course->id);
47 require_capability('moodle/grade:manage', $context);
49 // todo $PAGE->requires->js_module() should be used here instead
50 $PAGE->requires->js('/grade/edit/tree/functions.js');
52 /// return tracking object
53 $gpr = new grade_plugin_return(array('type'=>'edit', 'plugin'=>'tree', 'courseid'=>$courseid));
54 $returnurl = $gpr->get_return_url(null);
56 // Change category aggregation if requested
57 if (!is_null($category) && !is_null($aggregationtype) && confirm_sesskey()) {
58 if (!$grade_category = grade_category::fetch(array('id'=>$category, 'courseid'=>$courseid))) {
59 print_error('invalidcategoryid');
62 $data = new stdClass();
63 $data->aggregation = $aggregationtype;
64 grade_category::set_properties($grade_category, $data);
65 $grade_category->update();
67 grade_regrade_final_grades($courseid);
70 //first make sure we have proper final grades - we need it for locking changes
71 grade_regrade_final_grades($courseid);
73 // get the grading tree object
74 // note: total must be first for moving to work correctly, if you want it last moving code must be rewritten!
75 $gtree = new grade_tree($courseid, false, false);
77 if (empty($eid)) {
78 $element = null;
79 $object = null;
81 } else {
82 if (!$element = $gtree->locate_element($eid)) {
83 print_error('invalidelementid', '', $returnurl);
85 $object = $element['object'];
88 $switch = grade_get_setting($course->id, 'aggregationposition', $CFG->grade_aggregationposition);
90 $strgrades = get_string('grades');
91 $strgraderreport = get_string('graderreport', 'grades');
93 $moving = false;
94 $movingeid = false;
96 if ($action == 'moveselect') {
97 if ($eid and confirm_sesskey()) {
98 $movingeid = $eid;
99 $moving=true;
103 $grade_edit_tree = new grade_edit_tree($gtree, $movingeid, $gpr);
105 switch ($action) {
106 case 'delete':
107 if ($eid && confirm_sesskey()) {
108 if (!$grade_edit_tree->element_deletable($element)) {
109 // no deleting of external activities - they would be recreated anyway!
110 // exception is activity without grading or misconfigured activities
111 break;
113 $confirm = optional_param('confirm', 0, PARAM_BOOL);
115 if ($confirm) {
116 $object->delete('grade/report/grader/category');
117 redirect($returnurl);
119 } else {
120 $PAGE->set_title($strgrades . ': ' . $strgraderreport);
121 $PAGE->set_heading($course->fullname);
122 echo $OUTPUT->header();
123 $strdeletecheckfull = get_string('deletecheck', '', $object->get_name());
124 $optionsyes = array('eid'=>$eid, 'confirm'=>1, 'sesskey'=>sesskey(), 'id'=>$course->id, 'action'=>'delete');
125 $optionsno = array('id'=>$course->id);
126 $formcontinue = new single_button(new moodle_url('index.php', $optionsyes), get_string('yes'));
127 $formcancel = new single_button(new moodle_url('index.php', $optionsno), get_string('no'), 'get');
128 echo $OUTPUT->confirm($strdeletecheckfull, $formcontinue, $formcancel);
129 echo $OUTPUT->footer();
130 die;
133 break;
135 case 'autosort':
136 //TODO: implement autosorting based on order of mods on course page, categories first, manual items last
137 break;
139 case 'move':
140 if ($eid and confirm_sesskey()) {
141 $moveafter = required_param('moveafter', PARAM_ALPHANUM);
142 $first = optional_param('first', false, PARAM_BOOL); // If First is set to 1, it means the target is the first child of the category $moveafter
144 if(!$after_el = $gtree->locate_element($moveafter)) {
145 print_error('invalidelementid', '', $returnurl);
148 $after = $after_el['object'];
149 $sortorder = $after->get_sortorder();
151 if (!$first) {
152 $parent = $after->get_parent_category();
153 $object->set_parent($parent->id);
154 } else {
155 $object->set_parent($after->id);
158 $object->move_after_sortorder($sortorder);
160 redirect($returnurl);
162 break;
164 default:
165 break;
168 //if we go straight to the db to update an element we need to recreate the tree as
169 // $grade_edit_tree has already been constructed.
170 //Ideally we could do the updates through $grade_edit_tree to avoid recreating it
171 $recreatetree = false;
173 $normalisationmessage = null;
175 if ($data = data_submitted() and confirm_sesskey()) {
176 // Perform bulk actions first
177 if (!empty($data->bulkmove)) {
178 $elements = array();
180 foreach ($data as $key => $value) {
181 if (preg_match('/select_(ig[0-9]*)/', $key, $matches)) {
182 $elements[] = $matches[1];
186 $grade_edit_tree->move_elements($elements, $returnurl);
189 // Update weights (extra credits) on categories and items.
190 foreach ($data as $key => $value) {
191 if (preg_match('/^weight_([0-9]+)$/', $key, $matches)) {
192 $aid = $matches[1];
194 $value = unformat_float($value);
195 $value = clean_param($value, PARAM_FLOAT);
197 $grade_item = grade_item::fetch(array('id' => $aid, 'courseid' => $courseid));
199 // Convert weight to aggregation coef2.
200 $aggcoef = $grade_item->get_coefstring();
201 if ($aggcoef == 'aggregationcoefextraweightsum') {
202 // The field 'weight' should only be sent when the checkbox 'weighoverride' is checked,
203 // so there is not need to set weightoverride here, it is done below.
204 $value = $value / 100.0;
205 $grade_item->aggregationcoef2 = $value;
206 } else if ($aggcoef == 'aggregationcoefweight' || $aggcoef == 'aggregationcoefextraweight') {
207 $grade_item->aggregationcoef = $value;
210 $grade_item->update();
212 $recreatetree = true;
214 // Grade item checkbox inputs.
215 } elseif (preg_match('/^(weightoverride)_([0-9]+)$/', $key, $matches)) {
216 $param = $matches[1];
217 $aid = $matches[2];
218 $value = clean_param($value, PARAM_BOOL);
220 $grade_item = grade_item::fetch(array('id' => $aid, 'courseid' => $courseid));
221 $grade_item->$param = $value;
223 $grade_item->update();
225 $recreatetree = true;
229 grade_regrade_final_grades($courseid);
230 // Check to see if any weights were automatically adjusted.
231 // Run through the data to obtain all of the weight categories.
232 foreach ($data as $key => $notused) {
233 // We only want the weight entries.
234 if (preg_match('/^(weight)_([0-9]+)$/', $key, $matches)) {
235 // Fetch the weight for the grade item.
236 $gradeitemweight = grade_item::fetch(array('id' => $matches[2], 'courseid' => $courseid))->aggregationcoef2;
237 // Compare what was entered from the form with what was actually entered into the database.
238 if ($data->$matches[0] != ($gradeitemweight * 100)) {
239 // Send a notification that the weights were automatically adjusted.
240 $normalisationmessage = get_string('weightsadjusted', 'grades');
241 break;
247 print_grade_page_head($courseid, 'settings', 'setup', get_string('setupgradeslayout', 'grades'));
249 // Print Table of categories and items
250 echo $OUTPUT->box_start('gradetreebox generalbox');
252 echo '<form id="gradetreeform" method="post" action="'.$returnurl.'">';
253 echo '<div>';
254 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
256 //did we update something in the db and thus invalidate $grade_edit_tree?
257 if ($recreatetree) {
258 $grade_edit_tree = new grade_edit_tree($gtree, $movingeid, $gpr);
260 // Check to see if we have a normalisation message to send.
261 if (!empty($normalisationmessage)) {
262 echo $OUTPUT->notification($normalisationmessage, 'notifymessage');
265 echo html_writer::table($grade_edit_tree->table);
267 echo '<div id="gradetreesubmit">';
268 if (!$moving) {
269 echo '<input class="advanced" type="submit" value="'.get_string('savechanges').'" />';
272 // We don't print a bulk move menu if there are no other categories than course category
273 if (!$moving && count($grade_edit_tree->categories) > 1) {
274 echo '<br /><br />';
275 echo '<input type="hidden" name="bulkmove" value="0" id="bulkmoveinput" />';
276 $attributes = array('id'=>'menumoveafter', 'class' => 'ignoredirty');
277 echo html_writer::label(get_string('moveselectedto', 'grades'), 'menumoveafter');
278 echo html_writer::select($grade_edit_tree->categories, 'moveafter', '', array(''=>'choosedots'), $attributes);
279 $OUTPUT->add_action_handler(new component_action('change', 'submit_bulk_move'), 'menumoveafter');
280 echo '<div id="noscriptgradetreeform" class="hiddenifjs">
281 <input type="submit" value="'.get_string('go').'" />
282 </div>';
285 echo '</div>';
287 echo '</div></form>';
289 echo $OUTPUT->box_end();
291 // Print action buttons
292 echo $OUTPUT->container_start('buttons mdl-align');
294 if ($moving) {
295 echo $OUTPUT->single_button(new moodle_url('index.php', array('id'=>$course->id)), get_string('cancel'), 'get');
296 } else {
297 echo $OUTPUT->single_button(new moodle_url('category.php', array('courseid'=>$course->id)), get_string('addcategory', 'grades'), 'get');
298 echo $OUTPUT->single_button(new moodle_url('item.php', array('courseid'=>$course->id)), get_string('additem', 'grades'), 'get');
300 if (!empty($CFG->enableoutcomes)) {
301 echo $OUTPUT->single_button(new moodle_url('outcomeitem.php', array('courseid'=>$course->id)), get_string('addoutcomeitem', 'grades'), 'get');
304 //echo $OUTPUT->(new moodle_url('index.php', array('id'=>$course->id, 'action'=>'autosort')), get_string('autosort', 'grades'), 'get');
307 echo $OUTPUT->container_end();
309 $PAGE->requires->yui_module('moodle-core-formchangechecker',
310 'M.core_formchangechecker.init',
311 array(array(
312 'formid' => 'gradetreeform'
315 $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
317 echo $OUTPUT->footer();
318 die;