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. 13 // 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/>. 16 17 namespace core_grades\form; 18 19 defined('MOODLE_INTERNAL') || die; 20 21 use context; 22 use context_course; 23 use core_form\dynamic_form; 24 use grade_category; 25 use grade_item; 26 use grade_outcome; 27 use grade_plugin_return; 28 use moodle_url; 29 30 require_once($CFG->dirroot.'/grade/lib.php'); 31 32 /** 33 * Prints the add outcome gradebook form. 34 * 35 * @copyright 2023 Mathew May <mathew.solutions> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 37 * @package core_grades 38 */ 39 class add_outcome extends dynamic_form { 40 41 /** Grade plugin return tracking object. 42 * @var object $gpr 43 */ 44 public $gpr; 45 46 /** 47 * Helper function to grab the current grade outcome item based on information within the form. 48 * 49 * @return array 50 * @throws \moodle_exception 51 */ 52 private function get_gradeitem(): array { 53 $courseid = $this->optional_param('courseid', null, PARAM_INT); 54 $id = $this->optional_param('itemid', null, PARAM_INT); 55 56 if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { 57 // Redirect if outcomeid not present. 58 if (empty($gradeitem->outcomeid)) { 59 $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); 60 redirect($this->gpr->add_url_params($url)); 61 } 62 $item = $gradeitem->get_record_data(); 63 $parentcategory = $gradeitem->get_parent_category(); 64 if ($item->itemtype == 'mod') { 65 $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance, $item->courseid); 66 $item->cmid = $cm->id; 67 } else { 68 $item->cmid = 0; 69 } 70 } else { 71 $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); 72 $item = $gradeitem->get_record_data(); 73 $parentcategory = grade_category::fetch_course_category($courseid); 74 } 75 $item->parentcategory = $parentcategory->id; 76 77 if ($item->hidden > 1) { 78 $item->hiddenuntil = $item->hidden; 79 $item->hidden = 0; 80 } else { 81 $item->hiddenuntil = 0; 82 } 83 84 $item->locked = !empty($item->locked); 85 86 if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { 87 $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; 88 } else { 89 $item->aggregationcoef = format_float($item->aggregationcoef, 4); 90 } 91 if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { 92 $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); 93 } 94 $item->cancontrolvisibility = $gradeitem->can_control_visibility(); 95 return [ 96 'gradeitem' => $gradeitem, 97 'item' => $item 98 ]; 99 } 100 101 /** 102 * Form definition 103 * 104 * @return void 105 * @throws \coding_exception 106 * @throws \dml_exception 107 * @throws \moodle_exception 108 */ 109 protected function definition() { 110 $courseid = $this->optional_param('courseid', null, PARAM_INT); 111 $id = $this->optional_param('itemid', 0, PARAM_INT); 112 $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); 113 114 if ($gprplugin && ($gprplugin !== 'tree')) { 115 $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); 116 } else { 117 $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); 118 } 119 120 $mform =& $this->_form; 121 122 $local = $this->get_gradeitem(); 123 $gradeitem = $local['gradeitem']; 124 $item = $local['item']; 125 126 // Hidden elements. 127 $mform->addElement('hidden', 'id', 0); 128 $mform->setType('id', PARAM_INT); 129 $mform->addElement('hidden', 'courseid', $courseid); 130 $mform->setType('courseid', PARAM_INT); 131 $mform->addElement('hidden', 'itemid', $id); 132 $mform->setType('itemid', PARAM_INT); 133 134 // Allow setting of outcomes on module items too. 135 $outcomeoptions = []; 136 if ($outcomes = grade_outcome::fetch_all_available($courseid)) { 137 foreach ($outcomes as $outcome) { 138 $outcomeoptions[$outcome->id] = $outcome->get_name(); 139 } 140 } 141 142 // Visible elements. 143 $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); 144 $mform->addRule('itemname', get_string('required'), 'required', null, 'client'); 145 $mform->setType('itemname', PARAM_TEXT); 146 147 $mform->addElement('selectwithlink', 'outcomeid', get_string('outcome', 'grades'), $outcomeoptions); 148 $mform->addHelpButton('outcomeid', 'outcome', 'grades'); 149 $mform->addRule('outcomeid', get_string('required'), 'required'); 150 151 $options = [0 => get_string('none')]; 152 if ($coursemods = get_course_mods($courseid)) { 153 foreach ($coursemods as $coursemod) { 154 if ($mod = get_coursemodule_from_id($coursemod->modname, $coursemod->id)) { 155 $options[$coursemod->id] = format_string($mod->name); 156 } 157 } 158 } 159 $mform->addElement('select', 'cmid', get_string('linkedactivity', 'grades'), $options); 160 $mform->addHelpButton('cmid', 'linkedactivity', 'grades'); 161 $mform->setDefault('cmid', 0); 162 163 // Hiding. 164 $mform->addElement('checkbox', 'hidden', get_string('hidden', 'grades')); 165 $mform->addHelpButton('hidden', 'hidden', 'grades'); 166 167 // Locking. 168 $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); 169 $mform->addHelpButton('locked', 'locked', 'grades'); 170 171 // Parent category related settings. 172 $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); 173 $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); 174 175 $mform->addElement('text', 'aggregationcoef2', get_string('weight', 'grades')); 176 $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); 177 $mform->setType('aggregationcoef2', PARAM_RAW); 178 $mform->hideIf('aggregationcoef2', 'weightoverride'); 179 180 $options = []; 181 $coefstring = ''; 182 $categories = grade_category::fetch_all(['courseid' => $courseid]); 183 foreach ($categories as $cat) { 184 $cat->apply_forced_settings(); 185 $options[$cat->id] = $cat->get_name(); 186 if ($cat->is_aggregationcoef_used()) { 187 if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { 188 $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefweight') ? 189 'aggregationcoefweight' : 'aggregationcoef'; 190 } else if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { 191 $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 192 'aggregationcoefextrasum' : 'aggregationcoef'; 193 } else if ($cat->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { 194 $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextraweight') ? 195 'aggregationcoefextraweight' : 'aggregationcoef'; 196 } else if ($cat->aggregation == GRADE_AGGREGATE_SUM) { 197 $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 198 'aggregationcoefextrasum' : 'aggregationcoef'; 199 } else { 200 $coefstring = 'aggregationcoef'; 201 } 202 } else { 203 $mform->disabledIf('aggregationcoef', 'parentcategory', 'eq', $cat->id); 204 } 205 } 206 207 if (count($categories) > 1) { 208 $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); 209 $mform->disabledIf('parentcategory', 'cmid', 'noteq', 0); 210 } 211 212 if ($coefstring !== '') { 213 if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { 214 $coefstring = 'aggregationcoefextrasum'; 215 $mform->addElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); 216 } else { 217 $mform->addElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); 218 } 219 $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); 220 } 221 222 // Remove the aggregation coef element if not needed. 223 if ($gradeitem->is_course_item()) { 224 if ($mform->elementExists('parentcategory')) { 225 $mform->removeElement('parentcategory'); 226 } 227 if ($mform->elementExists('aggregationcoef')) { 228 $mform->removeElement('aggregationcoef'); 229 } 230 231 } else { 232 // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents. 233 if ($id > -1 && $mform->elementExists('parentcategory')) { 234 $mform->hardFreeze('parentcategory'); 235 } 236 237 $parentcategory = $gradeitem->get_parent_category(); 238 if (!$parentcategory) { 239 // If we do not have an id, we are creating a new grade item. 240 241 // Assign the course category to this grade item. 242 $parentcategory = grade_category::fetch_course_category($courseid); 243 $gradeitem->parent_category = $parentcategory; 244 } 245 246 $parentcategory->apply_forced_settings(); 247 248 if (!$parentcategory->is_aggregationcoef_used() || !$parentcategory->aggregateoutcomes) { 249 if ($mform->elementExists('aggregationcoef')) { 250 $mform->removeElement('aggregationcoef'); 251 } 252 } else { 253 // Fix label if needed. 254 $agg_el =& $mform->getElement('aggregationcoef'); 255 $aggcoef = ''; 256 if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { 257 $aggcoef = 'aggregationcoefweight'; 258 259 } else if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { 260 $aggcoef = 'aggregationcoefextrasum'; 261 262 } else if ($parentcategory->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { 263 $aggcoef = 'aggregationcoefextraweight'; 264 265 } else if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { 266 $aggcoef = 'aggregationcoefextrasum'; 267 } 268 269 if ($aggcoef !== '') { 270 $agg_el->setLabel(get_string($aggcoef, 'grades')); 271 $mform->addHelpButton('aggregationcoef', $aggcoef, 'grades'); 272 } 273 } 274 275 // Remove the natural weighting fields for other aggregations, 276 // or when the category does not aggregate outcomes. 277 if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || 278 !$parentcategory->aggregateoutcomes) { 279 if ($mform->elementExists('weightoverride')) { 280 $mform->removeElement('weightoverride'); 281 } 282 if ($mform->elementExists('aggregationcoef2')) { 283 $mform->removeElement('aggregationcoef2'); 284 } 285 } 286 } 287 288 $url = new moodle_url('/grade/edit/tree/outcomeitem.php', ['id' => $id, 'courseid' => $courseid]); 289 $url = $this->gpr->add_url_params($url); 290 $url = '<a class="showadvancedform" href="' . $url . '">' . get_string('showmore', 'form') .'</a>'; 291 $mform->addElement('static', 'advancedform', $url); 292 293 // Add return tracking info. 294 $this->gpr->add_mform_elements($mform); 295 296 $this->set_data($item); 297 } 298 299 /** 300 * Return form context 301 * 302 * @return context 303 */ 304 protected function get_context_for_dynamic_submission(): context { 305 $courseid = $this->optional_param('courseid', null, PARAM_INT); 306 return context_course::instance($courseid); 307 } 308 309 /** 310 * Check if current user has access to this form, otherwise throw exception 311 * 312 * @return void 313 * @throws \required_capability_exception 314 */ 315 protected function check_access_for_dynamic_submission(): void { 316 $courseid = $this->optional_param('courseid', null, PARAM_INT); 317 require_capability('moodle/grade:manage', context_course::instance($courseid)); 318 } 319 320 /** 321 * Load in existing data as form defaults 322 * 323 * @return void 324 */ 325 public function set_data_for_dynamic_submission(): void { 326 $this->set_data((object)[ 327 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 328 'itemid' => $this->optional_param('itemid', null, PARAM_INT) 329 ]); 330 } 331 332 /** 333 * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX 334 * 335 * @return moodle_url 336 * @throws \moodle_exception 337 */ 338 protected function get_page_url_for_dynamic_submission(): moodle_url { 339 $params = [ 340 'id' => $this->optional_param('courseid', null, PARAM_INT), 341 'itemid' => $this->optional_param('itemid', null, PARAM_INT), 342 ]; 343 return new moodle_url('/grade/edit/tree/index.php', $params); 344 } 345 346 /** 347 * Process the form submission, used if form was submitted via AJAX 348 * 349 * @return array 350 * @throws \moodle_exception 351 */ 352 public function process_dynamic_submission() { 353 global $DB; 354 $data = $this->get_data(); 355 356 $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); 357 $local = $this->get_gradeitem(); 358 $gradeitem = $local['gradeitem']; 359 $item = $local['item']; 360 $parentcategory = grade_category::fetch_course_category($data->courseid); 361 362 // Form submission handling. 363 // If unset, give the aggregation values a default based on parent aggregation method. 364 $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); 365 if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { 366 $data->aggregationcoef = $defaults['aggregationcoef']; 367 } 368 if (!isset($data->weightoverride)) { 369 $data->weightoverride = $defaults['weightoverride']; 370 } 371 372 if (property_exists($data, 'calculation')) { 373 $data->calculation = grade_item::normalize_formula($data->calculation, $data->courseid); 374 } 375 376 $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; 377 if (!$hide) { 378 $hide = empty($data->hidden) ? 0 : $data->hidden; 379 } 380 381 $locked = empty($data->locked) ? 0 : $data->locked; 382 $locktime = empty($data->locktime) ? 0 : $data->locktime; 383 384 $convert = ['gradepass', 'aggregationcoef', 'aggregationcoef2']; 385 foreach ($convert as $param) { 386 if (property_exists($data, $param)) { 387 $data->$param = unformat_float($data->$param); 388 } 389 } 390 if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { 391 $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; 392 } else { 393 $data->aggregationcoef2 = $defaults['aggregationcoef2']; 394 } 395 396 grade_item::set_properties($gradeitem, $data); 397 398 // Link this outcome item to the user specified linked activity. 399 if (empty($data->cmid) || $data->cmid == 0) { 400 // Manual item. 401 $gradeitem->itemtype = 'manual'; 402 $gradeitem->itemmodule = null; 403 $gradeitem->iteminstance = null; 404 $gradeitem->itemnumber = 0; 405 406 } else { 407 $params = [$data->cmid]; 408 $module = $DB->get_record_sql("SELECT cm.*, m.name as modname 409 FROM {modules} m, {course_modules} cm 410 WHERE cm.id = ? AND cm.module = m.id ", $params); 411 $gradeitem->itemtype = 'mod'; 412 $gradeitem->itemmodule = $module->modname; 413 $gradeitem->iteminstance = $module->instance; 414 415 if ($items = grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 416 'iteminstance' => $gradeitem->iteminstance, 'courseid' => $data->courseid])) { 417 if (!empty($gradeitem->id) && in_array($gradeitem, $items)) { 418 // No change needed. 419 } else { 420 $max = 999; 421 foreach ($items as $item) { 422 if (empty($item->outcomeid)) { 423 continue; 424 } 425 if ($item->itemnumber > $max) { 426 $max = $item->itemnumber; 427 } 428 } 429 $gradeitem->itemnumber = $max + 1; 430 } 431 } else { 432 $gradeitem->itemnumber = 1000; 433 } 434 } 435 436 // Fix scale used. 437 $outcome = grade_outcome::fetch(['id' => $data->outcomeid]); 438 $gradeitem->gradetype = GRADE_TYPE_SCALE; 439 $gradeitem->scaleid = $outcome->scaleid; // TODO: we might recalculate existing outcome grades when changing scale. 440 441 if (empty($gradeitem->id)) { 442 $gradeitem->insert(); 443 // Move next to activity if adding linked outcome. 444 if ($gradeitem->itemtype == 'mod') { 445 if ($linkeditem = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 446 'iteminstance' => $gradeitem->iteminstance, 'itemnumber' => 0, 'courseid' => $data->courseid])) { 447 $gradeitem->set_parent($linkeditem->categoryid); 448 $gradeitem->move_after_sortorder($linkeditem->sortorder); 449 } 450 } else { 451 // Set parent if needed. 452 if (isset($data->parentcategory)) { 453 $gradeitem->set_parent($data->parentcategory, false); 454 } 455 } 456 457 } else { 458 $gradeitem->update(); 459 } 460 461 if ($item->cancontrolvisibility) { 462 // Update hiding flag. 463 $gradeitem->set_hidden($hide, true); 464 } 465 466 $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. 467 $gradeitem->set_locked($locked, false, true); 468 return [ 469 'result' => true, 470 'url' => $url, 471 'errors' => [], 472 ]; 473 } 474 475 /** 476 * Form validation. 477 * 478 * @param array $data array of ("fieldname"=>value) of submitted data 479 * @param array $files array of uploaded files "element_name"=>tmp_file_path 480 * @return array of "element_name"=>"error_description" if there are errors, 481 * or an empty array if everything is OK (true allowed for backwards compatibility too). 482 */ 483 public function validation($data, $files): array { 484 $errors = []; 485 $local = $this->get_gradeitem(); 486 $gradeitem = $local['gradeitem']; 487 $item = $local['item']; 488 489 if (!grade_verify_idnumber($gradeitem->id, $item->courseid, $gradeitem)) { 490 $errors['idnumber'] = get_string('idnumbertaken'); 491 } 492 return $errors; 493 } 494 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body