See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
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 /** 18 * Functions used by gradebook plugins and reports. 19 * 20 * @package core_grades 21 * @copyright 2009 Petr Skoda and Nicolas Connault 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 require_once($CFG->libdir . '/gradelib.php'); 26 require_once($CFG->dirroot . '/grade/export/lib.php'); 27 28 use \core_grades\output\action_bar; 29 use \core_grades\output\general_action_bar; 30 31 /** 32 * This class iterates over all users that are graded in a course. 33 * Returns detailed info about users and their grades. 34 * 35 * @author Petr Skoda <skodak@moodle.org> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class graded_users_iterator { 39 40 /** 41 * The couse whose users we are interested in 42 */ 43 protected $course; 44 45 /** 46 * An array of grade items or null if only user data was requested 47 */ 48 protected $grade_items; 49 50 /** 51 * The group ID we are interested in. 0 means all groups. 52 */ 53 protected $groupid; 54 55 /** 56 * A recordset of graded users 57 */ 58 protected $users_rs; 59 60 /** 61 * A recordset of user grades (grade_grade instances) 62 */ 63 protected $grades_rs; 64 65 /** 66 * Array used when moving to next user while iterating through the grades recordset 67 */ 68 protected $gradestack; 69 70 /** 71 * The first field of the users table by which the array of users will be sorted 72 */ 73 protected $sortfield1; 74 75 /** 76 * Should sortfield1 be ASC or DESC 77 */ 78 protected $sortorder1; 79 80 /** 81 * The second field of the users table by which the array of users will be sorted 82 */ 83 protected $sortfield2; 84 85 /** 86 * Should sortfield2 be ASC or DESC 87 */ 88 protected $sortorder2; 89 90 /** 91 * Should users whose enrolment has been suspended be ignored? 92 */ 93 protected $onlyactive = false; 94 95 /** 96 * Enable user custom fields 97 */ 98 protected $allowusercustomfields = false; 99 100 /** 101 * List of suspended users in course. This includes users whose enrolment status is suspended 102 * or enrolment has expired or not started. 103 */ 104 protected $suspendedusers = array(); 105 106 /** 107 * Constructor 108 * 109 * @param object $course A course object 110 * @param array $grade_items array of grade items, if not specified only user info returned 111 * @param int $groupid iterate only group users if present 112 * @param string $sortfield1 The first field of the users table by which the array of users will be sorted 113 * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC) 114 * @param string $sortfield2 The second field of the users table by which the array of users will be sorted 115 * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC) 116 */ 117 public function __construct($course, $grade_items=null, $groupid=0, 118 $sortfield1='lastname', $sortorder1='ASC', 119 $sortfield2='firstname', $sortorder2='ASC') { 120 $this->course = $course; 121 $this->grade_items = $grade_items; 122 $this->groupid = $groupid; 123 $this->sortfield1 = $sortfield1; 124 $this->sortorder1 = $sortorder1; 125 $this->sortfield2 = $sortfield2; 126 $this->sortorder2 = $sortorder2; 127 128 $this->gradestack = array(); 129 } 130 131 /** 132 * Initialise the iterator 133 * 134 * @return boolean success 135 */ 136 public function init() { 137 global $CFG, $DB; 138 139 $this->close(); 140 141 export_verify_grades($this->course->id); 142 $course_item = grade_item::fetch_course_item($this->course->id); 143 if ($course_item->needsupdate) { 144 // Can not calculate all final grades - sorry. 145 return false; 146 } 147 148 $coursecontext = context_course::instance($this->course->id); 149 150 list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); 151 list($gradebookroles_sql, $params) = $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr'); 152 list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, '', 0, $this->onlyactive); 153 154 $params = array_merge($params, $enrolledparams, $relatedctxparams); 155 156 if ($this->groupid) { 157 $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id"; 158 $groupwheresql = "AND gm.groupid = :groupid"; 159 // $params contents: gradebookroles 160 $params['groupid'] = $this->groupid; 161 } else { 162 $groupsql = ""; 163 $groupwheresql = ""; 164 } 165 166 if (empty($this->sortfield1)) { 167 // We must do some sorting even if not specified. 168 $ofields = ", u.id AS usrt"; 169 $order = "usrt ASC"; 170 171 } else { 172 $ofields = ", u.$this->sortfield1 AS usrt1"; 173 $order = "usrt1 $this->sortorder1"; 174 if (!empty($this->sortfield2)) { 175 $ofields .= ", u.$this->sortfield2 AS usrt2"; 176 $order .= ", usrt2 $this->sortorder2"; 177 } 178 if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') { 179 // User order MUST be the same in both queries, 180 // must include the only unique user->id if not already present. 181 $ofields .= ", u.id AS usrt"; 182 $order .= ", usrt ASC"; 183 } 184 } 185 186 $userfields = 'u.*'; 187 $customfieldssql = ''; 188 if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) { 189 $customfieldscount = 0; 190 $customfieldsarray = grade_helper::get_user_profile_fields($this->course->id, $this->allowusercustomfields); 191 foreach ($customfieldsarray as $field) { 192 if (!empty($field->customid)) { 193 $customfieldssql .= " 194 LEFT JOIN (SELECT * FROM {user_info_data} 195 WHERE fieldid = :cf$customfieldscount) cf$customfieldscount 196 ON u.id = cf$customfieldscount.userid"; 197 $userfields .= ", cf$customfieldscount.data AS customfield_{$field->customid}"; 198 $params['cf'.$customfieldscount] = $field->customid; 199 $customfieldscount++; 200 } 201 } 202 } 203 204 $users_sql = "SELECT $userfields $ofields 205 FROM {user} u 206 JOIN ($enrolledsql) je ON je.id = u.id 207 $groupsql $customfieldssql 208 JOIN ( 209 SELECT DISTINCT ra.userid 210 FROM {role_assignments} ra 211 WHERE ra.roleid $gradebookroles_sql 212 AND ra.contextid $relatedctxsql 213 ) rainner ON rainner.userid = u.id 214 WHERE u.deleted = 0 215 $groupwheresql 216 ORDER BY $order"; 217 $this->users_rs = $DB->get_recordset_sql($users_sql, $params); 218 219 if (!$this->onlyactive) { 220 $context = context_course::instance($this->course->id); 221 $this->suspendedusers = get_suspended_userids($context); 222 } else { 223 $this->suspendedusers = array(); 224 } 225 226 if (!empty($this->grade_items)) { 227 $itemids = array_keys($this->grade_items); 228 list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items'); 229 $params = array_merge($params, $grades_params); 230 231 $grades_sql = "SELECT g.* $ofields 232 FROM {grade_grades} g 233 JOIN {user} u ON g.userid = u.id 234 JOIN ($enrolledsql) je ON je.id = u.id 235 $groupsql 236 JOIN ( 237 SELECT DISTINCT ra.userid 238 FROM {role_assignments} ra 239 WHERE ra.roleid $gradebookroles_sql 240 AND ra.contextid $relatedctxsql 241 ) rainner ON rainner.userid = u.id 242 WHERE u.deleted = 0 243 AND g.itemid $itemidsql 244 $groupwheresql 245 ORDER BY $order, g.itemid ASC"; 246 $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params); 247 } else { 248 $this->grades_rs = false; 249 } 250 251 return true; 252 } 253 254 /** 255 * Returns information about the next user 256 * @return mixed array of user info, all grades and feedback or null when no more users found 257 */ 258 public function next_user() { 259 if (!$this->users_rs) { 260 return false; // no users present 261 } 262 263 if (!$this->users_rs->valid()) { 264 if ($current = $this->_pop()) { 265 // this is not good - user or grades updated between the two reads above :-( 266 } 267 268 return false; // no more users 269 } else { 270 $user = $this->users_rs->current(); 271 $this->users_rs->next(); 272 } 273 274 // find grades of this user 275 $grade_records = array(); 276 while (true) { 277 if (!$current = $this->_pop()) { 278 break; // no more grades 279 } 280 281 if (empty($current->userid)) { 282 break; 283 } 284 285 if ($current->userid != $user->id) { 286 // grade of the next user, we have all for this user 287 $this->_push($current); 288 break; 289 } 290 291 $grade_records[$current->itemid] = $current; 292 } 293 294 $grades = array(); 295 $feedbacks = array(); 296 297 if (!empty($this->grade_items)) { 298 foreach ($this->grade_items as $grade_item) { 299 if (!isset($feedbacks[$grade_item->id])) { 300 $feedbacks[$grade_item->id] = new stdClass(); 301 } 302 if (array_key_exists($grade_item->id, $grade_records)) { 303 $feedbacks[$grade_item->id]->feedback = $grade_records[$grade_item->id]->feedback; 304 $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat; 305 unset($grade_records[$grade_item->id]->feedback); 306 unset($grade_records[$grade_item->id]->feedbackformat); 307 $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false); 308 } else { 309 $feedbacks[$grade_item->id]->feedback = ''; 310 $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE; 311 $grades[$grade_item->id] = 312 new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false); 313 } 314 $grades[$grade_item->id]->grade_item = $grade_item; 315 } 316 } 317 318 // Set user suspended status. 319 $user->suspendedenrolment = isset($this->suspendedusers[$user->id]); 320 $result = new stdClass(); 321 $result->user = $user; 322 $result->grades = $grades; 323 $result->feedbacks = $feedbacks; 324 return $result; 325 } 326 327 /** 328 * Close the iterator, do not forget to call this function 329 */ 330 public function close() { 331 if ($this->users_rs) { 332 $this->users_rs->close(); 333 $this->users_rs = null; 334 } 335 if ($this->grades_rs) { 336 $this->grades_rs->close(); 337 $this->grades_rs = null; 338 } 339 $this->gradestack = array(); 340 } 341 342 /** 343 * Should all enrolled users be exported or just those with an active enrolment? 344 * 345 * @param bool $onlyactive True to limit the export to users with an active enrolment 346 */ 347 public function require_active_enrolment($onlyactive = true) { 348 if (!empty($this->users_rs)) { 349 debugging('Calling require_active_enrolment() has no effect unless you call init() again', DEBUG_DEVELOPER); 350 } 351 $this->onlyactive = $onlyactive; 352 } 353 354 /** 355 * Allow custom fields to be included 356 * 357 * @param bool $allow Whether to allow custom fields or not 358 * @return void 359 */ 360 public function allow_user_custom_fields($allow = true) { 361 if ($allow) { 362 $this->allowusercustomfields = true; 363 } else { 364 $this->allowusercustomfields = false; 365 } 366 } 367 368 /** 369 * Add a grade_grade instance to the grade stack 370 * 371 * @param grade_grade $grade Grade object 372 * 373 * @return void 374 */ 375 private function _push($grade) { 376 array_push($this->gradestack, $grade); 377 } 378 379 380 /** 381 * Remove a grade_grade instance from the grade stack 382 * 383 * @return grade_grade current grade object 384 */ 385 private function _pop() { 386 global $DB; 387 if (empty($this->gradestack)) { 388 if (empty($this->grades_rs) || !$this->grades_rs->valid()) { 389 return null; // no grades present 390 } 391 392 $current = $this->grades_rs->current(); 393 394 $this->grades_rs->next(); 395 396 return $current; 397 } else { 398 return array_pop($this->gradestack); 399 } 400 } 401 } 402 403 /** 404 * Print a selection popup form of the graded users in a course. 405 * 406 * @deprecated since 2.0 407 * 408 * @param int $course id of the course 409 * @param string $actionpage The page receiving the data from the popoup form 410 * @param int $userid id of the currently selected user (or 'all' if they are all selected) 411 * @param int $groupid id of requested group, 0 means all 412 * @param int $includeall bool include all option 413 * @param bool $return If true, will return the HTML, otherwise, will print directly 414 * @return null 415 */ 416 function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) { 417 global $CFG, $USER, $OUTPUT; 418 return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall)); 419 } 420 421 function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) { 422 global $USER, $CFG; 423 424 if (is_null($userid)) { 425 $userid = $USER->id; 426 } 427 $coursecontext = context_course::instance($course->id); 428 $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); 429 $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); 430 $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); 431 $menu = array(); // Will be a list of userid => user name 432 $menususpendedusers = array(); // Suspended users go to a separate optgroup. 433 $gui = new graded_users_iterator($course, null, $groupid); 434 $gui->require_active_enrolment($showonlyactiveenrol); 435 $gui->init(); 436 $label = get_string('selectauser', 'grades'); 437 if ($includeall) { 438 $menu[0] = get_string('allusers', 'grades'); 439 $label = get_string('selectalloroneuser', 'grades'); 440 } 441 while ($userdata = $gui->next_user()) { 442 $user = $userdata->user; 443 $userfullname = fullname($user); 444 if ($user->suspendedenrolment) { 445 $menususpendedusers[$user->id] = $userfullname; 446 } else { 447 $menu[$user->id] = $userfullname; 448 } 449 } 450 $gui->close(); 451 452 if ($includeall) { 453 $menu[0] .= " (" . (count($menu) + count($menususpendedusers) - 1) . ")"; 454 } 455 456 if (!empty($menususpendedusers)) { 457 $menu[] = array(get_string('suspendedusers') => $menususpendedusers); 458 } 459 $gpr = new grade_plugin_return(array('type' => 'report', 'course' => $course, 'groupid' => $groupid)); 460 $select = new single_select( 461 new moodle_url('/grade/report/'.$report.'/index.php', $gpr->get_options()), 462 'userid', $menu, $userid 463 ); 464 $select->label = $label; 465 $select->formid = 'choosegradeuser'; 466 return $select; 467 } 468 469 /** 470 * Hide warning about changed grades during upgrade to 2.8. 471 * 472 * @param int $courseid The current course id. 473 */ 474 function hide_natural_aggregation_upgrade_notice($courseid) { 475 unset_config('show_sumofgrades_upgrade_' . $courseid); 476 } 477 478 /** 479 * Hide warning about changed grades during upgrade from 2.8.0-2.8.6 and 2.9.0. 480 * 481 * @param int $courseid The current course id. 482 */ 483 function grade_hide_min_max_grade_upgrade_notice($courseid) { 484 unset_config('show_min_max_grades_changed_' . $courseid); 485 } 486 487 /** 488 * Use the grade min and max from the grade_grade. 489 * 490 * This is reserved for core use after an upgrade. 491 * 492 * @param int $courseid The current course id. 493 */ 494 function grade_upgrade_use_min_max_from_grade_grade($courseid) { 495 grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_GRADE); 496 497 grade_force_full_regrading($courseid); 498 // Do this now, because it probably happened to late in the page load to be happen automatically. 499 grade_regrade_final_grades($courseid); 500 } 501 502 /** 503 * Use the grade min and max from the grade_item. 504 * 505 * This is reserved for core use after an upgrade. 506 * 507 * @param int $courseid The current course id. 508 */ 509 function grade_upgrade_use_min_max_from_grade_item($courseid) { 510 grade_set_setting($courseid, 'minmaxtouse', GRADE_MIN_MAX_FROM_GRADE_ITEM); 511 512 grade_force_full_regrading($courseid); 513 // Do this now, because it probably happened to late in the page load to be happen automatically. 514 grade_regrade_final_grades($courseid); 515 } 516 517 /** 518 * Hide warning about changed grades during upgrade to 2.8. 519 * 520 * @param int $courseid The current course id. 521 */ 522 function hide_aggregatesubcats_upgrade_notice($courseid) { 523 unset_config('show_aggregatesubcats_upgrade_' . $courseid); 524 } 525 526 /** 527 * Hide warning about changed grades due to bug fixes 528 * 529 * @param int $courseid The current course id. 530 */ 531 function hide_gradebook_calculations_freeze_notice($courseid) { 532 unset_config('gradebook_calculations_freeze_' . $courseid); 533 } 534 535 /** 536 * Print warning about changed grades during upgrade to 2.8. 537 * 538 * @param int $courseid The current course id. 539 * @param context $context The course context. 540 * @param string $thispage The relative path for the current page. E.g. /grade/report/user/index.php 541 * @param boolean $return return as string 542 * 543 * @return nothing or string if $return true 544 */ 545 function print_natural_aggregation_upgrade_notice($courseid, $context, $thispage, $return=false) { 546 global $CFG, $OUTPUT; 547 $html = ''; 548 549 // Do not do anything if they cannot manage the grades of this course. 550 if (!has_capability('moodle/grade:manage', $context)) { 551 return $html; 552 } 553 554 $hidesubcatswarning = optional_param('seenaggregatesubcatsupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); 555 $showsubcatswarning = get_config('core', 'show_aggregatesubcats_upgrade_' . $courseid); 556 $hidenaturalwarning = optional_param('seensumofgradesupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); 557 $shownaturalwarning = get_config('core', 'show_sumofgrades_upgrade_' . $courseid); 558 559 $hideminmaxwarning = optional_param('seenminmaxupgradedgrades', false, PARAM_BOOL) && confirm_sesskey(); 560 $showminmaxwarning = get_config('core', 'show_min_max_grades_changed_' . $courseid); 561 562 $useminmaxfromgradeitem = optional_param('useminmaxfromgradeitem', false, PARAM_BOOL) && confirm_sesskey(); 563 $useminmaxfromgradegrade = optional_param('useminmaxfromgradegrade', false, PARAM_BOOL) && confirm_sesskey(); 564 565 $minmaxtouse = grade_get_setting($courseid, 'minmaxtouse', $CFG->grade_minmaxtouse); 566 567 $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); 568 $acceptgradebookchanges = optional_param('acceptgradebookchanges', false, PARAM_BOOL) && confirm_sesskey(); 569 570 // Hide the warning if the user told it to go away. 571 if ($hidenaturalwarning) { 572 hide_natural_aggregation_upgrade_notice($courseid); 573 } 574 // Hide the warning if the user told it to go away. 575 if ($hidesubcatswarning) { 576 hide_aggregatesubcats_upgrade_notice($courseid); 577 } 578 579 // Hide the min/max warning if the user told it to go away. 580 if ($hideminmaxwarning) { 581 grade_hide_min_max_grade_upgrade_notice($courseid); 582 $showminmaxwarning = false; 583 } 584 585 if ($useminmaxfromgradegrade) { 586 // Revert to the new behaviour, we now use the grade_grade for min/max. 587 grade_upgrade_use_min_max_from_grade_grade($courseid); 588 grade_hide_min_max_grade_upgrade_notice($courseid); 589 $showminmaxwarning = false; 590 591 } else if ($useminmaxfromgradeitem) { 592 // Apply the new logic, we now use the grade_item for min/max. 593 grade_upgrade_use_min_max_from_grade_item($courseid); 594 grade_hide_min_max_grade_upgrade_notice($courseid); 595 $showminmaxwarning = false; 596 } 597 598 599 if (!$hidenaturalwarning && $shownaturalwarning) { 600 $message = get_string('sumofgradesupgradedgrades', 'grades'); 601 $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); 602 $urlparams = array( 'id' => $courseid, 603 'seensumofgradesupgradedgrades' => true, 604 'sesskey' => sesskey()); 605 $goawayurl = new moodle_url($thispage, $urlparams); 606 $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); 607 $html .= $OUTPUT->notification($message, 'notifysuccess'); 608 $html .= $goawaybutton; 609 } 610 611 if (!$hidesubcatswarning && $showsubcatswarning) { 612 $message = get_string('aggregatesubcatsupgradedgrades', 'grades'); 613 $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); 614 $urlparams = array( 'id' => $courseid, 615 'seenaggregatesubcatsupgradedgrades' => true, 616 'sesskey' => sesskey()); 617 $goawayurl = new moodle_url($thispage, $urlparams); 618 $goawaybutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); 619 $html .= $OUTPUT->notification($message, 'notifysuccess'); 620 $html .= $goawaybutton; 621 } 622 623 if ($showminmaxwarning) { 624 $hidemessage = get_string('upgradedgradeshidemessage', 'grades'); 625 $urlparams = array( 'id' => $courseid, 626 'seenminmaxupgradedgrades' => true, 627 'sesskey' => sesskey()); 628 629 $goawayurl = new moodle_url($thispage, $urlparams); 630 $hideminmaxbutton = $OUTPUT->single_button($goawayurl, $hidemessage, 'get'); 631 $moreinfo = html_writer::link(get_docs_url(get_string('minmaxtouse_link', 'grades')), get_string('moreinfo'), 632 array('target' => '_blank')); 633 634 if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_ITEM) { 635 // Show the message that there were min/max issues that have been resolved. 636 $message = get_string('minmaxupgradedgrades', 'grades') . ' ' . $moreinfo; 637 638 $revertmessage = get_string('upgradedminmaxrevertmessage', 'grades'); 639 $urlparams = array('id' => $courseid, 640 'useminmaxfromgradegrade' => true, 641 'sesskey' => sesskey()); 642 $reverturl = new moodle_url($thispage, $urlparams); 643 $revertbutton = $OUTPUT->single_button($reverturl, $revertmessage, 'get'); 644 645 $html .= $OUTPUT->notification($message); 646 $html .= $revertbutton . $hideminmaxbutton; 647 648 } else if ($minmaxtouse == GRADE_MIN_MAX_FROM_GRADE_GRADE) { 649 // Show the warning that there are min/max issues that have not be resolved. 650 $message = get_string('minmaxupgradewarning', 'grades') . ' ' . $moreinfo; 651 652 $fixmessage = get_string('minmaxupgradefixbutton', 'grades'); 653 $urlparams = array('id' => $courseid, 654 'useminmaxfromgradeitem' => true, 655 'sesskey' => sesskey()); 656 $fixurl = new moodle_url($thispage, $urlparams); 657 $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get'); 658 659 $html .= $OUTPUT->notification($message); 660 $html .= $fixbutton . $hideminmaxbutton; 661 } 662 } 663 664 if ($gradebookcalculationsfreeze) { 665 if ($acceptgradebookchanges) { 666 // Accept potential changes in grades caused by extra credit bug MDL-49257. 667 hide_gradebook_calculations_freeze_notice($courseid); 668 $courseitem = grade_item::fetch_course_item($courseid); 669 $courseitem->force_regrading(); 670 grade_regrade_final_grades($courseid); 671 672 $html .= $OUTPUT->notification(get_string('gradebookcalculationsuptodate', 'grades'), 'notifysuccess'); 673 } else { 674 // Show the warning that there may be extra credit weights problems. 675 $a = new stdClass(); 676 $a->gradebookversion = $gradebookcalculationsfreeze; 677 if (preg_match('/(\d{8,})/', $CFG->release, $matches)) { 678 $a->currentversion = $matches[1]; 679 } else { 680 $a->currentversion = $CFG->release; 681 } 682 $a->url = get_docs_url('Gradebook_calculation_changes'); 683 $message = get_string('gradebookcalculationswarning', 'grades', $a); 684 685 $fixmessage = get_string('gradebookcalculationsfixbutton', 'grades'); 686 $urlparams = array('id' => $courseid, 687 'acceptgradebookchanges' => true, 688 'sesskey' => sesskey()); 689 $fixurl = new moodle_url($thispage, $urlparams); 690 $fixbutton = $OUTPUT->single_button($fixurl, $fixmessage, 'get'); 691 692 $html .= $OUTPUT->notification($message); 693 $html .= $fixbutton; 694 } 695 } 696 697 if (!empty($html)) { 698 $html = html_writer::tag('div', $html, array('class' => 'core_grades_notices')); 699 } 700 701 if ($return) { 702 return $html; 703 } else { 704 echo $html; 705 } 706 } 707 708 /** 709 * grade_get_plugin_info 710 * 711 * @param int $courseid The course id 712 * @param string $active_type type of plugin on current page - import, export, report or edit 713 * @param string $active_plugin active plugin type - grader, user, cvs, ... 714 * 715 * @return array 716 */ 717 function grade_get_plugin_info($courseid, $active_type, $active_plugin) { 718 global $CFG, $SITE; 719 720 $context = context_course::instance($courseid); 721 722 $plugin_info = array(); 723 $count = 0; 724 $active = ''; 725 $url_prefix = $CFG->wwwroot . '/grade/'; 726 727 // Language strings 728 $plugin_info['strings'] = grade_helper::get_plugin_strings(); 729 730 if ($reports = grade_helper::get_plugins_reports($courseid)) { 731 $plugin_info['report'] = $reports; 732 } 733 734 if ($settings = grade_helper::get_info_manage_settings($courseid)) { 735 $plugin_info['settings'] = $settings; 736 } 737 738 if ($scale = grade_helper::get_info_scales($courseid)) { 739 $plugin_info['scale'] = array('view'=>$scale); 740 } 741 742 if ($outcomes = grade_helper::get_info_outcomes($courseid)) { 743 $plugin_info['outcome'] = $outcomes; 744 } 745 746 if ($letters = grade_helper::get_info_letters($courseid)) { 747 $plugin_info['letter'] = $letters; 748 } 749 750 if ($imports = grade_helper::get_plugins_import($courseid)) { 751 $plugin_info['import'] = $imports; 752 } 753 754 if ($exports = grade_helper::get_plugins_export($courseid)) { 755 $plugin_info['export'] = $exports; 756 } 757 758 // Let other plugins add plugins here so that we get extra tabs 759 // in the gradebook. 760 $callbacks = get_plugins_with_function('extend_gradebook_plugininfo', 'lib.php'); 761 foreach ($callbacks as $plugins) { 762 foreach ($plugins as $pluginfunction) { 763 $plugin_info = $pluginfunction($plugin_info, $courseid); 764 } 765 } 766 767 foreach ($plugin_info as $plugin_type => $plugins) { 768 if (!empty($plugins->id) && $active_plugin == $plugins->id) { 769 $plugin_info['strings']['active_plugin_str'] = $plugins->string; 770 break; 771 } 772 foreach ($plugins as $plugin) { 773 if (is_a($plugin, grade_plugin_info::class)) { 774 if ($plugin_type === $active_type && $active_plugin == $plugin->id) { 775 $plugin_info['strings']['active_plugin_str'] = $plugin->string; 776 } 777 } 778 } 779 } 780 781 return $plugin_info; 782 } 783 784 /** 785 * Load a valid list of gradable users in a course. 786 * 787 * @param int $courseid The course ID. 788 * @param int|null $groupid The group ID (optional). 789 * @param bool $onlyactiveenrol Include only active enrolments. 790 * @return array $users A list of enrolled gradable users. 791 */ 792 function get_gradable_users(int $courseid, ?int $groupid = null, bool $onlyactiveenrol = false): array { 793 $course = get_course($courseid); 794 // Create a graded_users_iterator because it will properly check the groups etc. 795 $gui = new graded_users_iterator($course, null, $groupid); 796 $gui->require_active_enrolment($onlyactiveenrol); 797 $gui->init(); 798 799 // Flatten the users. 800 $users = []; 801 while ($user = $gui->next_user()) { 802 $users[$user->user->id] = $user->user; 803 } 804 $gui->close(); 805 806 return $users; 807 } 808 809 /** 810 * A simple class containing info about grade plugins. 811 * Can be subclassed for special rules 812 * 813 * @package core_grades 814 * @copyright 2009 Nicolas Connault 815 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 816 */ 817 class grade_plugin_info { 818 /** 819 * A unique id for this plugin 820 * 821 * @var mixed 822 */ 823 public $id; 824 /** 825 * A URL to access this plugin 826 * 827 * @var mixed 828 */ 829 public $link; 830 /** 831 * The name of this plugin 832 * 833 * @var mixed 834 */ 835 public $string; 836 /** 837 * Another grade_plugin_info object, parent of the current one 838 * 839 * @var mixed 840 */ 841 public $parent; 842 843 /** 844 * Constructor 845 * 846 * @param int $id A unique id for this plugin 847 * @param string $link A URL to access this plugin 848 * @param string $string The name of this plugin 849 * @param object $parent Another grade_plugin_info object, parent of the current one 850 * 851 * @return void 852 */ 853 public function __construct($id, $link, $string, $parent=null) { 854 $this->id = $id; 855 $this->link = $link; 856 $this->string = $string; 857 $this->parent = $parent; 858 } 859 } 860 861 /** 862 * Prints the page headers, breadcrumb trail, page heading, (optional) navigation and for any gradebook page. 863 * All gradebook pages MUST use these functions in favour of the usual print_header(), print_header_simple(), 864 * print_heading() etc. 865 * 866 * @param int $courseid Course id 867 * @param string $active_type The type of the current page (report, settings, 868 * import, export, scales, outcomes, letters) 869 * @param string|null $active_plugin The plugin of the current page (grader, fullview etc...) 870 * @param string|bool $heading The heading of the page. Tries to guess if none is given 871 * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function 872 * @param string|bool $buttons Additional buttons to display on the page 873 * @param boolean $shownavigation should the gradebook navigation be shown? 874 * @param string|null $headerhelpidentifier The help string identifier if required. 875 * @param string|null $headerhelpcomponent The component for the help string. 876 * @param stdClass|null $user The user object for use with the user context header. 877 * @param action_bar|null $actionbar The actions bar which will be displayed on the page if $shownavigation is set 878 * to true. If $actionbar is not explicitly defined, the general action bar 879 * (\core_grades\output\general_action_bar) will be used by default. 880 * @param boolean $showtitle If set to false just show course full name as a title. 881 * @return string HTML code or nothing if $return == false 882 */ 883 function print_grade_page_head(int $courseid, string $active_type, ?string $active_plugin = null, $heading = false, 884 bool $return = false, $buttons = false, bool $shownavigation = true, ?string $headerhelpidentifier = null, 885 ?string $headerhelpcomponent = null, ?stdClass $user = null, ?action_bar $actionbar = null, $showtitle = true) { 886 global $CFG, $OUTPUT, $PAGE, $USER; 887 888 if ($heading !== false) { 889 // Make sure to trim heading, including the non-breaking space character. 890 $heading = str_replace(" ", " ", $heading); 891 $heading = trim($heading); 892 } 893 894 // Put a warning on all gradebook pages if the course has modules currently scheduled for background deletion. 895 require_once($CFG->dirroot . '/course/lib.php'); 896 if (course_modules_pending_deletion($courseid, true)) { 897 \core\notification::add(get_string('gradesmoduledeletionpendingwarning', 'grades'), 898 \core\output\notification::NOTIFY_WARNING); 899 } 900 901 if ($active_type === 'preferences') { 902 // In Moodle 2.8 report preferences were moved under 'settings'. Allow backward compatibility for 3rd party grade reports. 903 $active_type = 'settings'; 904 } 905 906 $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin); 907 908 // Determine the string of the active plugin 909 $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading; 910 $stractive_type = $plugin_info['strings'][$active_type]; 911 912 if (!$showtitle) { 913 $title = $PAGE->course->fullname; 914 } else if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) { 915 $title = $PAGE->course->fullname.': ' . $stractive_type . ': ' . $stractive_plugin; 916 } else { 917 $title = $PAGE->course->fullname.': ' . $stractive_plugin; 918 } 919 920 if ($active_type == 'report') { 921 $PAGE->set_pagelayout('report'); 922 } else { 923 $PAGE->set_pagelayout('admin'); 924 } 925 $coursecontext = context_course::instance($courseid); 926 // Title will be constituted by information starting from the unique identifying information for the page. 927 if ($heading) { 928 // If heading is supplied, use this for the page title. 929 $uniquetitle = $heading; 930 } else if (in_array($active_type, ['report', 'settings'])) { 931 // For grade reports or settings pages of grade plugins, use the plugin name for the unique title. 932 $uniquetitle = $stractive_plugin; 933 // But if editing mode is turned on, check if the report plugin has an editing mode title string and use it if present. 934 if ($PAGE->user_is_editing() && $active_type === 'report') { 935 $strcomponent = "gradereport_{$active_plugin}"; 936 if (get_string_manager()->string_exists('editingmode_title', $strcomponent)) { 937 $uniquetitle = get_string('editingmode_title', $strcomponent); 938 } 939 } 940 } else { 941 $uniquetitle = $stractive_type . ': ' . $stractive_plugin; 942 } 943 $titlecomponents = [ 944 $uniquetitle, 945 $coursecontext->get_context_name(false), 946 ]; 947 $PAGE->set_title(implode(moodle_page::TITLE_SEPARATOR, $titlecomponents)); 948 $PAGE->set_heading($title); 949 $PAGE->set_secondary_active_tab('grades'); 950 951 if ($buttons instanceof single_button) { 952 $buttons = $OUTPUT->render($buttons); 953 } 954 $PAGE->set_button($buttons); 955 if ($courseid != SITEID) { 956 grade_extend_settings($plugin_info, $courseid); 957 } 958 959 // Set the current report as active in the breadcrumbs. 960 if ($active_plugin !== null && $reportnav = $PAGE->settingsnav->find($active_plugin, navigation_node::TYPE_SETTING)) { 961 $reportnav->make_active(); 962 } 963 964 $returnval = $OUTPUT->header(); 965 966 if (!$return) { 967 echo $returnval; 968 } 969 970 // Guess heading if not given explicitly 971 if (!$heading) { 972 $heading = $stractive_plugin; 973 } 974 975 if (!$showtitle) { 976 $heading = ''; 977 } 978 979 if ($shownavigation) { 980 $renderer = $PAGE->get_renderer('core_grades'); 981 // If the navigation action bar is not explicitly defined, use the general (default) action bar. 982 if (!$actionbar) { 983 $actionbar = new general_action_bar($PAGE->context, $PAGE->url, $active_type, $active_plugin); 984 } 985 986 if ($return) { 987 $returnval .= $renderer->render_action_bar($actionbar); 988 } else { 989 echo $renderer->render_action_bar($actionbar); 990 } 991 } 992 993 $output = ''; 994 // Add a help dialogue box if provided. 995 if (isset($headerhelpidentifier) && !empty($heading)) { 996 $output = $OUTPUT->heading_with_help($heading, $headerhelpidentifier, $headerhelpcomponent); 997 } else if (isset($user)) { 998 $renderer = $PAGE->get_renderer('core_grades'); 999 // If the user is viewing their own grade report, no need to show the "Message" 1000 // and "Add to contact" buttons in the user heading. 1001 $showuserbuttons = $user->id != $USER->id; 1002 $output = $renderer->user_heading($user, $courseid, $showuserbuttons); 1003 } else if (!empty($heading)) { 1004 $output = $OUTPUT->heading($heading); 1005 } 1006 1007 if ($return) { 1008 $returnval .= $output; 1009 } else { 1010 echo $output; 1011 } 1012 1013 $returnval .= print_natural_aggregation_upgrade_notice($courseid, $coursecontext, $PAGE->url, $return); 1014 1015 if ($return) { 1016 return $returnval; 1017 } 1018 } 1019 1020 /** 1021 * Utility class used for return tracking when using edit and other forms in grade plugins 1022 * 1023 * @package core_grades 1024 * @copyright 2009 Nicolas Connault 1025 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1026 */ 1027 class grade_plugin_return { 1028 /** 1029 * Type of grade plugin (e.g. 'edit', 'report') 1030 * 1031 * @var string 1032 */ 1033 public $type; 1034 /** 1035 * Name of grade plugin (e.g. 'grader', 'overview') 1036 * 1037 * @var string 1038 */ 1039 public $plugin; 1040 /** 1041 * Course id being viewed 1042 * 1043 * @var int 1044 */ 1045 public $courseid; 1046 /** 1047 * Id of user whose information is being viewed/edited 1048 * 1049 * @var int 1050 */ 1051 public $userid; 1052 /** 1053 * Id of group for which information is being viewed/edited 1054 * 1055 * @var int 1056 */ 1057 public $groupid; 1058 /** 1059 * Current page # within output 1060 * 1061 * @var int 1062 */ 1063 public $page; 1064 1065 /** 1066 * Constructor 1067 * 1068 * @param array $params - associative array with return parameters, if not supplied parameter are taken from _GET or _POST 1069 */ 1070 public function __construct($params = []) { 1071 $this->type = optional_param('gpr_type', null, PARAM_SAFEDIR); 1072 $this->plugin = optional_param('gpr_plugin', null, PARAM_PLUGIN); 1073 $this->courseid = optional_param('gpr_courseid', null, PARAM_INT); 1074 $this->userid = optional_param('gpr_userid', null, PARAM_INT); 1075 $this->groupid = optional_param('gpr_groupid', null, PARAM_INT); 1076 $this->page = optional_param('gpr_page', null, PARAM_INT); 1077 1078 foreach ($params as $key => $value) { 1079 if (property_exists($this, $key)) { 1080 $this->$key = $value; 1081 } 1082 } 1083 // Allow course object rather than id to be used to specify course 1084 // - avoid unnecessary use of get_course. 1085 if (array_key_exists('course', $params)) { 1086 $course = $params['course']; 1087 $this->courseid = $course->id; 1088 } else { 1089 $course = null; 1090 } 1091 // If group has been explicitly set in constructor parameters, 1092 // we should respect that. 1093 if (!array_key_exists('groupid', $params)) { 1094 // Otherwise, 'group' in request parameters is a request for a change. 1095 // In that case, or if we have no group at all, we should get groupid from 1096 // groups_get_course_group, which will do some housekeeping as well as 1097 // give us the correct value. 1098 $changegroup = optional_param('group', -1, PARAM_INT); 1099 if ($changegroup !== -1 or (empty($this->groupid) and !empty($this->courseid))) { 1100 if ($course === null) { 1101 $course = get_course($this->courseid); 1102 } 1103 $this->groupid = groups_get_course_group($course, true); 1104 } 1105 } 1106 } 1107 1108 /** 1109 * Old syntax of class constructor. Deprecated in PHP7. 1110 * 1111 * @deprecated since Moodle 3.1 1112 */ 1113 public function grade_plugin_return($params = null) { 1114 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 1115 self::__construct($params); 1116 } 1117 1118 /** 1119 * Returns return parameters as options array suitable for buttons. 1120 * @return array options 1121 */ 1122 public function get_options() { 1123 if (empty($this->type)) { 1124 return array(); 1125 } 1126 1127 $params = array(); 1128 1129 if (!empty($this->plugin)) { 1130 $params['plugin'] = $this->plugin; 1131 } 1132 1133 if (!empty($this->courseid)) { 1134 $params['id'] = $this->courseid; 1135 } 1136 1137 if (!empty($this->userid)) { 1138 $params['userid'] = $this->userid; 1139 } 1140 1141 if (!empty($this->groupid)) { 1142 $params['group'] = $this->groupid; 1143 } 1144 1145 if (!empty($this->page)) { 1146 $params['page'] = $this->page; 1147 } 1148 1149 return $params; 1150 } 1151 1152 /** 1153 * Returns return url 1154 * 1155 * @param string $default default url when params not set 1156 * @param array $extras Extra URL parameters 1157 * 1158 * @return string url 1159 */ 1160 public function get_return_url($default, $extras=null) { 1161 global $CFG; 1162 1163 if (empty($this->type) or empty($this->plugin)) { 1164 return $default; 1165 } 1166 1167 $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php'; 1168 $glue = '?'; 1169 1170 if (!empty($this->courseid)) { 1171 $url .= $glue.'id='.$this->courseid; 1172 $glue = '&'; 1173 } 1174 1175 if (!empty($this->userid)) { 1176 $url .= $glue.'userid='.$this->userid; 1177 $glue = '&'; 1178 } 1179 1180 if (!empty($this->groupid)) { 1181 $url .= $glue.'group='.$this->groupid; 1182 $glue = '&'; 1183 } 1184 1185 if (!empty($this->page)) { 1186 $url .= $glue.'page='.$this->page; 1187 $glue = '&'; 1188 } 1189 1190 if (!empty($extras)) { 1191 foreach ($extras as $key=>$value) { 1192 $url .= $glue.$key.'='.$value; 1193 $glue = '&'; 1194 } 1195 } 1196 1197 return $url; 1198 } 1199 1200 /** 1201 * Returns string with hidden return tracking form elements. 1202 * @return string 1203 */ 1204 public function get_form_fields() { 1205 if (empty($this->type)) { 1206 return ''; 1207 } 1208 1209 $result = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />'; 1210 1211 if (!empty($this->plugin)) { 1212 $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />'; 1213 } 1214 1215 if (!empty($this->courseid)) { 1216 $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />'; 1217 } 1218 1219 if (!empty($this->userid)) { 1220 $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />'; 1221 } 1222 1223 if (!empty($this->groupid)) { 1224 $result .= '<input type="hidden" name="gpr_groupid" value="'.$this->groupid.'" />'; 1225 } 1226 1227 if (!empty($this->page)) { 1228 $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />'; 1229 } 1230 return $result; 1231 } 1232 1233 /** 1234 * Add hidden elements into mform 1235 * 1236 * @param object &$mform moodle form object 1237 * 1238 * @return void 1239 */ 1240 public function add_mform_elements(&$mform) { 1241 if (empty($this->type)) { 1242 return; 1243 } 1244 1245 $mform->addElement('hidden', 'gpr_type', $this->type); 1246 $mform->setType('gpr_type', PARAM_SAFEDIR); 1247 1248 if (!empty($this->plugin)) { 1249 $mform->addElement('hidden', 'gpr_plugin', $this->plugin); 1250 $mform->setType('gpr_plugin', PARAM_PLUGIN); 1251 } 1252 1253 if (!empty($this->courseid)) { 1254 $mform->addElement('hidden', 'gpr_courseid', $this->courseid); 1255 $mform->setType('gpr_courseid', PARAM_INT); 1256 } 1257 1258 if (!empty($this->userid)) { 1259 $mform->addElement('hidden', 'gpr_userid', $this->userid); 1260 $mform->setType('gpr_userid', PARAM_INT); 1261 } 1262 1263 if (!empty($this->groupid)) { 1264 $mform->addElement('hidden', 'gpr_groupid', $this->groupid); 1265 $mform->setType('gpr_groupid', PARAM_INT); 1266 } 1267 1268 if (!empty($this->page)) { 1269 $mform->addElement('hidden', 'gpr_page', $this->page); 1270 $mform->setType('gpr_page', PARAM_INT); 1271 } 1272 } 1273 1274 /** 1275 * Add return tracking params into url 1276 * 1277 * @param moodle_url $url A URL 1278 * @return moodle_url with return tracking params 1279 */ 1280 public function add_url_params(moodle_url $url): moodle_url { 1281 if (empty($this->type)) { 1282 return $url; 1283 } 1284 1285 $url->param('gpr_type', $this->type); 1286 1287 if (!empty($this->plugin)) { 1288 $url->param('gpr_plugin', $this->plugin); 1289 } 1290 1291 if (!empty($this->courseid)) { 1292 $url->param('gpr_courseid' ,$this->courseid); 1293 } 1294 1295 if (!empty($this->userid)) { 1296 $url->param('gpr_userid', $this->userid); 1297 } 1298 1299 if (!empty($this->groupid)) { 1300 $url->param('gpr_groupid', $this->groupid); 1301 } 1302 1303 if (!empty($this->page)) { 1304 $url->param('gpr_page', $this->page); 1305 } 1306 1307 return $url; 1308 } 1309 } 1310 1311 /** 1312 * Function central to gradebook for building and printing the navigation (breadcrumb trail). 1313 * 1314 * @param string $path The path of the calling script (using __FILE__?) 1315 * @param string $pagename The language string to use as the last part of the navigation (non-link) 1316 * @param mixed $id Either a plain integer (assuming the key is 'id') or 1317 * an array of keys and values (e.g courseid => $courseid, itemid...) 1318 * 1319 * @return string 1320 */ 1321 function grade_build_nav($path, $pagename=null, $id=null) { 1322 global $CFG, $COURSE, $PAGE; 1323 1324 $strgrades = get_string('grades', 'grades'); 1325 1326 // Parse the path and build navlinks from its elements 1327 $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash 1328 $path = substr($path, $dirroot_length); 1329 $path = str_replace('\\', '/', $path); 1330 1331 $path_elements = explode('/', $path); 1332 1333 $path_elements_count = count($path_elements); 1334 1335 // First link is always 'grade' 1336 $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id))); 1337 1338 $link = null; 1339 $numberofelements = 3; 1340 1341 // Prepare URL params string 1342 $linkparams = array(); 1343 if (!is_null($id)) { 1344 if (is_array($id)) { 1345 foreach ($id as $idkey => $idvalue) { 1346 $linkparams[$idkey] = $idvalue; 1347 } 1348 } else { 1349 $linkparams['id'] = $id; 1350 } 1351 } 1352 1353 $navlink4 = null; 1354 1355 // Remove file extensions from filenames 1356 foreach ($path_elements as $key => $filename) { 1357 $path_elements[$key] = str_replace('.php', '', $filename); 1358 } 1359 1360 // Second level links 1361 switch ($path_elements[1]) { 1362 case 'edit': // No link 1363 if ($path_elements[3] != 'index.php') { 1364 $numberofelements = 4; 1365 } 1366 break; 1367 case 'import': // No link 1368 break; 1369 case 'export': // No link 1370 break; 1371 case 'report': 1372 // $id is required for this link. Do not print it if $id isn't given 1373 if (!is_null($id)) { 1374 $link = new moodle_url('/grade/report/index.php', $linkparams); 1375 } 1376 1377 if ($path_elements[2] == 'grader') { 1378 $numberofelements = 4; 1379 } 1380 break; 1381 1382 default: 1383 // If this element isn't among the ones already listed above, it isn't supported, throw an error. 1384 debugging("grade_build_nav() doesn't support ". $path_elements[1] . 1385 " as the second path element after 'grade'."); 1386 return false; 1387 } 1388 $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link); 1389 1390 // Third level links 1391 if (empty($pagename)) { 1392 $pagename = get_string($path_elements[2], 'grades'); 1393 } 1394 1395 switch ($numberofelements) { 1396 case 3: 1397 $PAGE->navbar->add($pagename, $link); 1398 break; 1399 case 4: 1400 if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') { 1401 $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams)); 1402 } 1403 $PAGE->navbar->add($pagename); 1404 break; 1405 } 1406 1407 return ''; 1408 } 1409 1410 /** 1411 * General structure representing grade items in course 1412 * 1413 * @package core_grades 1414 * @copyright 2009 Nicolas Connault 1415 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1416 */ 1417 class grade_structure { 1418 public $context; 1419 1420 public $courseid; 1421 1422 /** 1423 * Reference to modinfo for current course (for performance, to save 1424 * retrieving it from courseid every time). Not actually set except for 1425 * the grade_tree type. 1426 * @var course_modinfo 1427 */ 1428 public $modinfo; 1429 1430 /** 1431 * 1D array of grade items only 1432 */ 1433 public $items; 1434 1435 /** 1436 * Returns icon of element 1437 * 1438 * @param array &$element An array representing an element in the grade_tree 1439 * @param bool $spacerifnone return spacer if no icon found 1440 * 1441 * @return string icon or spacer 1442 */ 1443 public function get_element_icon(&$element, $spacerifnone=false) { 1444 global $CFG, $OUTPUT; 1445 require_once $CFG->libdir.'/filelib.php'; 1446 1447 $outputstr = ''; 1448 1449 // Object holding pix_icon information before instantiation. 1450 $icon = new stdClass(); 1451 $icon->attributes = array( 1452 'class' => 'icon itemicon' 1453 ); 1454 $icon->component = 'moodle'; 1455 1456 $none = true; 1457 switch ($element['type']) { 1458 case 'item': 1459 case 'courseitem': 1460 case 'categoryitem': 1461 $none = false; 1462 1463 $is_course = $element['object']->is_course_item(); 1464 $is_category = $element['object']->is_category_item(); 1465 $is_scale = $element['object']->gradetype == GRADE_TYPE_SCALE; 1466 $is_value = $element['object']->gradetype == GRADE_TYPE_VALUE; 1467 $is_outcome = !empty($element['object']->outcomeid); 1468 1469 if ($element['object']->is_calculated()) { 1470 $icon->pix = 'i/calc'; 1471 $icon->title = s(get_string('calculatedgrade', 'grades')); 1472 1473 } else if (($is_course or $is_category) and ($is_scale or $is_value)) { 1474 if ($category = $element['object']->get_item_category()) { 1475 $aggrstrings = grade_helper::get_aggregation_strings(); 1476 $stragg = $aggrstrings[$category->aggregation]; 1477 1478 $icon->pix = 'i/calc'; 1479 $icon->title = s($stragg); 1480 1481 switch ($category->aggregation) { 1482 case GRADE_AGGREGATE_MEAN: 1483 case GRADE_AGGREGATE_MEDIAN: 1484 case GRADE_AGGREGATE_WEIGHTED_MEAN: 1485 case GRADE_AGGREGATE_WEIGHTED_MEAN2: 1486 case GRADE_AGGREGATE_EXTRACREDIT_MEAN: 1487 $icon->pix = 'i/agg_mean'; 1488 break; 1489 case GRADE_AGGREGATE_SUM: 1490 $icon->pix = 'i/agg_sum'; 1491 break; 1492 } 1493 } 1494 1495 } else if ($element['object']->itemtype == 'mod') { 1496 // Prevent outcomes displaying the same icon as the activity they are attached to. 1497 if ($is_outcome) { 1498 $icon->pix = 'i/outcomes'; 1499 $icon->title = s(get_string('outcome', 'grades')); 1500 } else { 1501 $modinfo = get_fast_modinfo($element['object']->courseid); 1502 $module = $element['object']->itemmodule; 1503 $instanceid = $element['object']->iteminstance; 1504 if (isset($modinfo->instances[$module][$instanceid])) { 1505 $icon->url = $modinfo->instances[$module][$instanceid]->get_icon_url(); 1506 } else { 1507 $icon->pix = 'monologo'; 1508 $icon->component = $element['object']->itemmodule; 1509 } 1510 $icon->title = s(get_string('modulename', $element['object']->itemmodule)); 1511 } 1512 } else if ($element['object']->itemtype == 'manual') { 1513 if ($element['object']->is_outcome_item()) { 1514 $icon->pix = 'i/outcomes'; 1515 $icon->title = s(get_string('outcome', 'grades')); 1516 } else { 1517 $icon->pix = 'i/manual_item'; 1518 $icon->title = s(get_string('manualitem', 'grades')); 1519 } 1520 } 1521 break; 1522 1523 case 'category': 1524 $none = false; 1525 $icon->pix = 'i/folder'; 1526 $icon->title = s(get_string('category', 'grades')); 1527 break; 1528 } 1529 1530 if ($none) { 1531 if ($spacerifnone) { 1532 $outputstr = $OUTPUT->spacer() . ' '; 1533 } 1534 } else if (isset($icon->url)) { 1535 $outputstr = html_writer::img($icon->url, $icon->title, $icon->attributes); 1536 } else { 1537 $outputstr = $OUTPUT->pix_icon($icon->pix, $icon->title, $icon->component, $icon->attributes); 1538 } 1539 1540 return $outputstr; 1541 } 1542 1543 /** 1544 * Returns the string that describes the type of the element. 1545 * 1546 * @param array $element An array representing an element in the grade_tree 1547 * @return string The string that describes the type of the grade element 1548 */ 1549 public function get_element_type_string(array $element): string { 1550 // If the element is a grade category. 1551 if ($element['type'] == 'category') { 1552 return get_string('category', 'grades'); 1553 } 1554 // If the element is a grade item. 1555 if (in_array($element['type'], ['item', 'courseitem', 'categoryitem'])) { 1556 // If calculated grade item. 1557 if ($element['object']->is_calculated()) { 1558 return get_string('calculatedgrade', 'grades'); 1559 } 1560 // If aggregated type grade item. 1561 if ($element['object']->is_aggregate_item()) { 1562 return get_string('aggregation', 'core_grades'); 1563 } 1564 // If external grade item (module, plugin, etc.). 1565 if ($element['object']->is_external_item()) { 1566 // If outcome grade item. 1567 if ($element['object']->is_outcome_item()) { 1568 return get_string('outcome', 'grades'); 1569 } 1570 return get_string('modulename', $element['object']->itemmodule); 1571 } 1572 // If manual grade item. 1573 if ($element['object']->itemtype == 'manual') { 1574 // If outcome grade item. 1575 if ($element['object']->is_outcome_item()) { 1576 return get_string('outcome', 'grades'); 1577 } 1578 return get_string('manualitem', 'grades'); 1579 } 1580 } 1581 1582 return ''; 1583 } 1584 1585 /** 1586 * Returns name of element optionally with icon and link 1587 * 1588 * @param array &$element An array representing an element in the grade_tree 1589 * @param bool $withlink Whether or not this header has a link 1590 * @param bool $icon Whether or not to display an icon with this header 1591 * @param bool $spacerifnone return spacer if no icon found 1592 * @param bool $withdescription Show description if defined by this item. 1593 * @param bool $fulltotal If the item is a category total, returns $categoryname."total" 1594 * instead of "Category total" or "Course total" 1595 * 1596 * @return string header 1597 */ 1598 public function get_element_header(&$element, $withlink = false, $icon = true, $spacerifnone = false, 1599 $withdescription = false, $fulltotal = false) { 1600 $header = ''; 1601 1602 if ($icon) { 1603 $header .= $this->get_element_icon($element, $spacerifnone); 1604 } 1605 1606 $title = $element['object']->get_name($fulltotal); 1607 $titleunescaped = $element['object']->get_name($fulltotal, false); 1608 $header .= $title; 1609 1610 if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and 1611 $element['type'] != 'courseitem') { 1612 return $header; 1613 } 1614 1615 if ($withlink && $url = $this->get_activity_link($element)) { 1616 $a = new stdClass(); 1617 $a->name = get_string('modulename', $element['object']->itemmodule); 1618 $a->title = $titleunescaped; 1619 $title = get_string('linktoactivity', 'grades', $a); 1620 1621 $header = html_writer::link($url, $header, array('title' => $title, 'class' => 'gradeitemheader')); 1622 } else { 1623 $header = html_writer::span($header, 'gradeitemheader', array('title' => $titleunescaped, 'tabindex' => '0')); 1624 } 1625 1626 if ($withdescription) { 1627 $desc = $element['object']->get_description(); 1628 if (!empty($desc)) { 1629 $header .= '<div class="gradeitemdescription">' . s($desc) . '</div><div class="gradeitemdescriptionfiller"></div>'; 1630 } 1631 } 1632 1633 return $header; 1634 } 1635 1636 private function get_activity_link($element) { 1637 global $CFG; 1638 /** @var array static cache of the grade.php file existence flags */ 1639 static $hasgradephp = array(); 1640 1641 $itemtype = $element['object']->itemtype; 1642 $itemmodule = $element['object']->itemmodule; 1643 $iteminstance = $element['object']->iteminstance; 1644 $itemnumber = $element['object']->itemnumber; 1645 1646 // Links only for module items that have valid instance, module and are 1647 // called from grade_tree with valid modinfo 1648 if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) { 1649 return null; 1650 } 1651 1652 // Get $cm efficiently and with visibility information using modinfo 1653 $instances = $this->modinfo->get_instances(); 1654 if (empty($instances[$itemmodule][$iteminstance])) { 1655 return null; 1656 } 1657 $cm = $instances[$itemmodule][$iteminstance]; 1658 1659 // Do not add link if activity is not visible to the current user 1660 if (!$cm->uservisible) { 1661 return null; 1662 } 1663 1664 if (!array_key_exists($itemmodule, $hasgradephp)) { 1665 if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) { 1666 $hasgradephp[$itemmodule] = true; 1667 } else { 1668 $hasgradephp[$itemmodule] = false; 1669 } 1670 } 1671 1672 // If module has grade.php, link to that, otherwise view.php 1673 if ($hasgradephp[$itemmodule]) { 1674 $args = array('id' => $cm->id, 'itemnumber' => $itemnumber); 1675 if (isset($element['userid'])) { 1676 $args['userid'] = $element['userid']; 1677 } 1678 return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args); 1679 } else { 1680 return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id)); 1681 } 1682 } 1683 1684 /** 1685 * Returns URL of a page that is supposed to contain detailed grade analysis 1686 * 1687 * At the moment, only activity modules are supported. The method generates link 1688 * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber, 1689 * gradeid and userid. If the grade.php does not exist, null is returned. 1690 * 1691 * @return moodle_url|null URL or null if unable to construct it 1692 */ 1693 public function get_grade_analysis_url(grade_grade $grade) { 1694 global $CFG; 1695 /** @var array static cache of the grade.php file existence flags */ 1696 static $hasgradephp = array(); 1697 1698 if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) { 1699 throw new coding_exception('Passed grade without the associated grade item'); 1700 } 1701 $item = $grade->grade_item; 1702 1703 if (!$item->is_external_item()) { 1704 // at the moment, only activity modules are supported 1705 return null; 1706 } 1707 if ($item->itemtype !== 'mod') { 1708 throw new coding_exception('Unknown external itemtype: '.$item->itemtype); 1709 } 1710 if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) { 1711 return null; 1712 } 1713 1714 if (!array_key_exists($item->itemmodule, $hasgradephp)) { 1715 if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) { 1716 $hasgradephp[$item->itemmodule] = true; 1717 } else { 1718 $hasgradephp[$item->itemmodule] = false; 1719 } 1720 } 1721 1722 if (!$hasgradephp[$item->itemmodule]) { 1723 return null; 1724 } 1725 1726 $instances = $this->modinfo->get_instances(); 1727 if (empty($instances[$item->itemmodule][$item->iteminstance])) { 1728 return null; 1729 } 1730 $cm = $instances[$item->itemmodule][$item->iteminstance]; 1731 if (!$cm->uservisible) { 1732 return null; 1733 } 1734 1735 $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array( 1736 'id' => $cm->id, 1737 'itemid' => $item->id, 1738 'itemnumber' => $item->itemnumber, 1739 'gradeid' => $grade->id, 1740 'userid' => $grade->userid, 1741 )); 1742 1743 return $url; 1744 } 1745 1746 /** 1747 * Returns an action icon leading to the grade analysis page 1748 * 1749 * @param grade_grade $grade 1750 * @return string 1751 */ 1752 public function get_grade_analysis_icon(grade_grade $grade) { 1753 global $OUTPUT; 1754 1755 $url = $this->get_grade_analysis_url($grade); 1756 if (is_null($url)) { 1757 return ''; 1758 } 1759 1760 $title = get_string('gradeanalysis', 'core_grades'); 1761 return $OUTPUT->action_icon($url, new pix_icon('t/preview', ''), null, 1762 ['title' => $title, 'aria-label' => $title]); 1763 } 1764 1765 /** 1766 * Returns an action menu for the grade. 1767 * 1768 * @param grade_grade $grade A grade_grade object 1769 * @return string 1770 */ 1771 public function get_grade_action_menu(grade_grade $grade) : string { 1772 global $OUTPUT; 1773 1774 $menuitems = []; 1775 1776 $url = $this->get_grade_analysis_url($grade); 1777 if ($url) { 1778 $title = get_string('gradeanalysis', 'core_grades'); 1779 $menuitems[] = new action_menu_link_secondary($url, null, $title); 1780 } 1781 1782 if ($menuitems) { 1783 $menu = new action_menu($menuitems); 1784 $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions')); 1785 $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center'; 1786 $menu->set_menu_trigger($icon, $extraclasses); 1787 $menu->set_menu_left(); 1788 1789 return $OUTPUT->render($menu); 1790 } else { 1791 return ''; 1792 } 1793 } 1794 1795 /** 1796 * Returns the grade eid - the grade may not exist yet. 1797 * 1798 * @param grade_grade $grade_grade A grade_grade object 1799 * 1800 * @return string eid 1801 */ 1802 public function get_grade_eid($grade_grade) { 1803 if (empty($grade_grade->id)) { 1804 return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid; 1805 } else { 1806 return 'g'.$grade_grade->id; 1807 } 1808 } 1809 1810 /** 1811 * Returns the grade_item eid 1812 * @param grade_item $grade_item A grade_item object 1813 * @return string eid 1814 */ 1815 public function get_item_eid($grade_item) { 1816 return 'ig'.$grade_item->id; 1817 } 1818 1819 /** 1820 * Given a grade_tree element, returns an array of parameters 1821 * used to build an icon for that element. 1822 * 1823 * @param array $element An array representing an element in the grade_tree 1824 * 1825 * @return array 1826 */ 1827 public function get_params_for_iconstr($element) { 1828 $strparams = new stdClass(); 1829 $strparams->category = ''; 1830 $strparams->itemname = ''; 1831 $strparams->itemmodule = ''; 1832 1833 if (!method_exists($element['object'], 'get_name')) { 1834 return $strparams; 1835 } 1836 1837 $strparams->itemname = html_to_text($element['object']->get_name()); 1838 1839 // If element name is categorytotal, get the name of the parent category 1840 if ($strparams->itemname == get_string('categorytotal', 'grades')) { 1841 $parent = $element['object']->get_parent_category(); 1842 $strparams->category = $parent->get_name() . ' '; 1843 } else { 1844 $strparams->category = ''; 1845 } 1846 1847 $strparams->itemmodule = null; 1848 if (isset($element['object']->itemmodule)) { 1849 $strparams->itemmodule = $element['object']->itemmodule; 1850 } 1851 return $strparams; 1852 } 1853 1854 /** 1855 * Return a reset icon for the given element. 1856 * 1857 * @param array $element An array representing an element in the grade_tree 1858 * @param object $gpr A grade_plugin_return object 1859 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 1860 * @return string|action_menu_link 1861 */ 1862 public function get_reset_icon($element, $gpr, $returnactionmenulink = false) { 1863 global $CFG, $OUTPUT; 1864 1865 // Limit to category items set to use the natural weights aggregation method, and users 1866 // with the capability to manage grades. 1867 if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM || 1868 !has_capability('moodle/grade:manage', $this->context)) { 1869 return $returnactionmenulink ? null : ''; 1870 } 1871 1872 $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element)); 1873 $url = new moodle_url('/grade/edit/tree/action.php', array( 1874 'id' => $this->courseid, 1875 'action' => 'resetweights', 1876 'eid' => $element['eid'], 1877 'sesskey' => sesskey(), 1878 )); 1879 1880 if ($returnactionmenulink) { 1881 return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/reset', $str), 1882 get_string('resetweightsshort', 'grades')); 1883 } else { 1884 return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str)); 1885 } 1886 } 1887 1888 /** 1889 * Return edit icon for give element 1890 * 1891 * @param array $element An array representing an element in the grade_tree 1892 * @param object $gpr A grade_plugin_return object 1893 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 1894 * @return string|action_menu_link 1895 */ 1896 public function get_edit_icon($element, $gpr, $returnactionmenulink = false) { 1897 global $CFG, $OUTPUT; 1898 1899 if (!has_capability('moodle/grade:manage', $this->context)) { 1900 if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) { 1901 // oki - let them override grade 1902 } else { 1903 return $returnactionmenulink ? null : ''; 1904 } 1905 } 1906 1907 static $strfeedback = null; 1908 static $streditgrade = null; 1909 if (is_null($streditgrade)) { 1910 $streditgrade = get_string('editgrade', 'grades'); 1911 $strfeedback = get_string('feedback'); 1912 } 1913 1914 $strparams = $this->get_params_for_iconstr($element); 1915 1916 $object = $element['object']; 1917 1918 switch ($element['type']) { 1919 case 'item': 1920 case 'categoryitem': 1921 case 'courseitem': 1922 $stredit = get_string('editverbose', 'grades', $strparams); 1923 if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) { 1924 $url = new moodle_url('/grade/edit/tree/item.php', 1925 array('courseid' => $this->courseid, 'id' => $object->id)); 1926 } else { 1927 $url = new moodle_url('/grade/edit/tree/outcomeitem.php', 1928 array('courseid' => $this->courseid, 'id' => $object->id)); 1929 } 1930 break; 1931 1932 case 'category': 1933 $stredit = get_string('editverbose', 'grades', $strparams); 1934 $url = new moodle_url('/grade/edit/tree/category.php', 1935 array('courseid' => $this->courseid, 'id' => $object->id)); 1936 break; 1937 1938 case 'grade': 1939 $stredit = $streditgrade; 1940 if (empty($object->id)) { 1941 $url = new moodle_url('/grade/edit/tree/grade.php', 1942 array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid)); 1943 } else { 1944 $url = new moodle_url('/grade/edit/tree/grade.php', 1945 array('courseid' => $this->courseid, 'id' => $object->id)); 1946 } 1947 if (!empty($object->feedback)) { 1948 $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat))); 1949 } 1950 break; 1951 1952 default: 1953 $url = null; 1954 } 1955 1956 if ($url) { 1957 if ($returnactionmenulink) { 1958 return new action_menu_link_secondary($gpr->add_url_params($url), 1959 new pix_icon('t/edit', $stredit), 1960 get_string('editsettings')); 1961 } else { 1962 return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit)); 1963 } 1964 1965 } else { 1966 return $returnactionmenulink ? null : ''; 1967 } 1968 } 1969 1970 /** 1971 * Return hiding icon for give element 1972 * 1973 * @param array $element An array representing an element in the grade_tree 1974 * @param object $gpr A grade_plugin_return object 1975 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 1976 * @return string|action_menu_link 1977 */ 1978 public function get_hiding_icon($element, $gpr, $returnactionmenulink = false) { 1979 global $CFG, $OUTPUT; 1980 1981 if (!$element['object']->can_control_visibility()) { 1982 return $returnactionmenulink ? null : ''; 1983 } 1984 1985 if (!has_capability('moodle/grade:manage', $this->context) and 1986 !has_capability('moodle/grade:hide', $this->context)) { 1987 return $returnactionmenulink ? null : ''; 1988 } 1989 1990 $strparams = $this->get_params_for_iconstr($element); 1991 $strshow = get_string('showverbose', 'grades', $strparams); 1992 $strhide = get_string('hideverbose', 'grades', $strparams); 1993 1994 $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); 1995 $url = $gpr->add_url_params($url); 1996 1997 if ($element['object']->is_hidden()) { 1998 $type = 'show'; 1999 $tooltip = $strshow; 2000 2001 // Change the icon and add a tooltip showing the date 2002 if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) { 2003 $type = 'hiddenuntil'; 2004 $tooltip = get_string('hiddenuntildate', 'grades', 2005 userdate($element['object']->get_hidden())); 2006 } 2007 2008 $url->param('action', 'show'); 2009 2010 if ($returnactionmenulink) { 2011 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/'.$type, $tooltip), get_string('show')); 2012 } else { 2013 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon'))); 2014 } 2015 2016 } else { 2017 $url->param('action', 'hide'); 2018 if ($returnactionmenulink) { 2019 $hideicon = new action_menu_link_secondary($url, new pix_icon('t/hide', $strhide), get_string('hide')); 2020 } else { 2021 $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide)); 2022 } 2023 } 2024 2025 return $hideicon; 2026 } 2027 2028 /** 2029 * Return locking icon for given element 2030 * 2031 * @param array $element An array representing an element in the grade_tree 2032 * @param object $gpr A grade_plugin_return object 2033 * 2034 * @return string 2035 */ 2036 public function get_locking_icon($element, $gpr) { 2037 global $CFG, $OUTPUT; 2038 2039 $strparams = $this->get_params_for_iconstr($element); 2040 $strunlock = get_string('unlockverbose', 'grades', $strparams); 2041 $strlock = get_string('lockverbose', 'grades', $strparams); 2042 2043 $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid'])); 2044 $url = $gpr->add_url_params($url); 2045 2046 // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon 2047 if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) { 2048 $strparamobj = new stdClass(); 2049 $strparamobj->itemname = $element['object']->grade_item->itemname; 2050 $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj); 2051 2052 $action = html_writer::tag('span', $OUTPUT->pix_icon('t/locked', $strnonunlockable), 2053 array('class' => 'action-icon')); 2054 2055 } else if ($element['object']->is_locked()) { 2056 $type = 'unlock'; 2057 $tooltip = $strunlock; 2058 2059 // Change the icon and add a tooltip showing the date 2060 if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) { 2061 $type = 'locktime'; 2062 $tooltip = get_string('locktimedate', 'grades', 2063 userdate($element['object']->get_locktime())); 2064 } 2065 2066 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) { 2067 $action = ''; 2068 } else { 2069 $url->param('action', 'unlock'); 2070 $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon'))); 2071 } 2072 2073 } else { 2074 if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) { 2075 $action = ''; 2076 } else { 2077 $url->param('action', 'lock'); 2078 $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock)); 2079 } 2080 } 2081 2082 return $action; 2083 } 2084 2085 /** 2086 * Return calculation icon for given element 2087 * 2088 * @param array $element An array representing an element in the grade_tree 2089 * @param object $gpr A grade_plugin_return object 2090 * @param bool $returnactionmenulink return the instance of action_menu_link instead of string 2091 * @return string|action_menu_link 2092 */ 2093 public function get_calculation_icon($element, $gpr, $returnactionmenulink = false) { 2094 global $CFG, $OUTPUT; 2095 if (!has_capability('moodle/grade:manage', $this->context)) { 2096 return $returnactionmenulink ? null : ''; 2097 } 2098 2099 $type = $element['type']; 2100 $object = $element['object']; 2101 2102 if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') { 2103 $strparams = $this->get_params_for_iconstr($element); 2104 $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams); 2105 2106 $is_scale = $object->gradetype == GRADE_TYPE_SCALE; 2107 $is_value = $object->gradetype == GRADE_TYPE_VALUE; 2108 2109 // show calculation icon only when calculation possible 2110 if (!$object->is_external_item() and ($is_scale or $is_value)) { 2111 if ($object->is_calculated()) { 2112 $icon = 't/calc'; 2113 } else { 2114 $icon = 't/calc_off'; 2115 } 2116 2117 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id)); 2118 $url = $gpr->add_url_params($url); 2119 if ($returnactionmenulink) { 2120 return new action_menu_link_secondary($url, 2121 new pix_icon($icon, $streditcalculation), 2122 get_string('editcalculation', 'grades')); 2123 } else { 2124 return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation)); 2125 } 2126 } 2127 } 2128 2129 return $returnactionmenulink ? null : ''; 2130 } 2131 } 2132 2133 /** 2134 * Flat structure similar to grade tree. 2135 * 2136 * @uses grade_structure 2137 * @package core_grades 2138 * @copyright 2009 Nicolas Connault 2139 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2140 */ 2141 class grade_seq extends grade_structure { 2142 2143 /** 2144 * 1D array of elements 2145 */ 2146 public $elements; 2147 2148 /** 2149 * Constructor, retrieves and stores array of all grade_category and grade_item 2150 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. 2151 * 2152 * @param int $courseid The course id 2153 * @param bool $category_grade_last category grade item is the last child 2154 * @param bool $nooutcomes Whether or not outcomes should be included 2155 */ 2156 public function __construct($courseid, $category_grade_last=false, $nooutcomes=false) { 2157 global $USER, $CFG; 2158 2159 $this->courseid = $courseid; 2160 $this->context = context_course::instance($courseid); 2161 2162 // get course grade tree 2163 $top_element = grade_category::fetch_course_tree($courseid, true); 2164 2165 $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes); 2166 2167 foreach ($this->elements as $key=>$unused) { 2168 $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object']; 2169 } 2170 } 2171 2172 /** 2173 * Old syntax of class constructor. Deprecated in PHP7. 2174 * 2175 * @deprecated since Moodle 3.1 2176 */ 2177 public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) { 2178 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 2179 self::__construct($courseid, $category_grade_last, $nooutcomes); 2180 } 2181 2182 /** 2183 * Static recursive helper - makes the grade_item for category the last children 2184 * 2185 * @param array &$element The seed of the recursion 2186 * @param bool $category_grade_last category grade item is the last child 2187 * @param bool $nooutcomes Whether or not outcomes should be included 2188 * 2189 * @return array 2190 */ 2191 public function flatten(&$element, $category_grade_last, $nooutcomes) { 2192 if (empty($element['children'])) { 2193 return array(); 2194 } 2195 $children = array(); 2196 2197 foreach ($element['children'] as $sortorder=>$unused) { 2198 if ($nooutcomes and $element['type'] != 'category' and 2199 $element['children'][$sortorder]['object']->is_outcome_item()) { 2200 continue; 2201 } 2202 $children[] = $element['children'][$sortorder]; 2203 } 2204 unset($element['children']); 2205 2206 if ($category_grade_last and count($children) > 1 and 2207 ( 2208 $children[0]['type'] === 'courseitem' or 2209 $children[0]['type'] === 'categoryitem' 2210 ) 2211 ) { 2212 $cat_item = array_shift($children); 2213 array_push($children, $cat_item); 2214 } 2215 2216 $result = array(); 2217 foreach ($children as $child) { 2218 if ($child['type'] == 'category') { 2219 $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes); 2220 } else { 2221 $child['eid'] = 'i'.$child['object']->id; 2222 $result[$child['object']->id] = $child; 2223 } 2224 } 2225 2226 return $result; 2227 } 2228 2229 /** 2230 * Parses the array in search of a given eid and returns a element object with 2231 * information about the element it has found. 2232 * 2233 * @param int $eid Gradetree Element ID 2234 * 2235 * @return object element 2236 */ 2237 public function locate_element($eid) { 2238 // it is a grade - construct a new object 2239 if (strpos($eid, 'n') === 0) { 2240 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { 2241 return null; 2242 } 2243 2244 $itemid = $matches[1]; 2245 $userid = $matches[2]; 2246 2247 //extra security check - the grade item must be in this tree 2248 if (!$item_el = $this->locate_element('ig'.$itemid)) { 2249 return null; 2250 } 2251 2252 // $gradea->id may be null - means does not exist yet 2253 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); 2254 2255 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2256 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); 2257 2258 } else if (strpos($eid, 'g') === 0) { 2259 $id = (int) substr($eid, 1); 2260 if (!$grade = grade_grade::fetch(array('id'=>$id))) { 2261 return null; 2262 } 2263 //extra security check - the grade item must be in this tree 2264 if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { 2265 return null; 2266 } 2267 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2268 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); 2269 } 2270 2271 // it is a category or item 2272 foreach ($this->elements as $element) { 2273 if ($element['eid'] == $eid) { 2274 return $element; 2275 } 2276 } 2277 2278 return null; 2279 } 2280 } 2281 2282 /** 2283 * This class represents a complete tree of categories, grade_items and final grades, 2284 * organises as an array primarily, but which can also be converted to other formats. 2285 * It has simple method calls with complex implementations, allowing for easy insertion, 2286 * deletion and moving of items and categories within the tree. 2287 * 2288 * @uses grade_structure 2289 * @package core_grades 2290 * @copyright 2009 Nicolas Connault 2291 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2292 */ 2293 class grade_tree extends grade_structure { 2294 2295 /** 2296 * The basic representation of the tree as a hierarchical, 3-tiered array. 2297 * @var object $top_element 2298 */ 2299 public $top_element; 2300 2301 /** 2302 * 2D array of grade items and categories 2303 * @var array $levels 2304 */ 2305 public $levels; 2306 2307 /** 2308 * Grade items 2309 * @var array $items 2310 */ 2311 public $items; 2312 2313 /** 2314 * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item 2315 * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed. 2316 * 2317 * @param int $courseid The Course ID 2318 * @param bool $fillers include fillers and colspans, make the levels var "rectangular" 2319 * @param bool $category_grade_last category grade item is the last child 2320 * @param array $collapsed array of collapsed categories 2321 * @param bool $nooutcomes Whether or not outcomes should be included 2322 */ 2323 public function __construct($courseid, $fillers=true, $category_grade_last=false, 2324 $collapsed=null, $nooutcomes=false) { 2325 global $USER, $CFG, $COURSE, $DB; 2326 2327 $this->courseid = $courseid; 2328 $this->levels = array(); 2329 $this->context = context_course::instance($courseid); 2330 2331 if (!empty($COURSE->id) && $COURSE->id == $this->courseid) { 2332 $course = $COURSE; 2333 } else { 2334 $course = $DB->get_record('course', array('id' => $this->courseid)); 2335 } 2336 $this->modinfo = get_fast_modinfo($course); 2337 2338 // get course grade tree 2339 $this->top_element = grade_category::fetch_course_tree($courseid, true); 2340 2341 // collapse the categories if requested 2342 if (!empty($collapsed)) { 2343 grade_tree::category_collapse($this->top_element, $collapsed); 2344 } 2345 2346 // no otucomes if requested 2347 if (!empty($nooutcomes)) { 2348 grade_tree::no_outcomes($this->top_element); 2349 } 2350 2351 // move category item to last position in category 2352 if ($category_grade_last) { 2353 grade_tree::category_grade_last($this->top_element); 2354 } 2355 2356 if ($fillers) { 2357 // inject fake categories == fillers 2358 grade_tree::inject_fillers($this->top_element, 0); 2359 // add colspans to categories and fillers 2360 grade_tree::inject_colspans($this->top_element); 2361 } 2362 2363 grade_tree::fill_levels($this->levels, $this->top_element, 0); 2364 2365 } 2366 2367 /** 2368 * Old syntax of class constructor. Deprecated in PHP7. 2369 * 2370 * @deprecated since Moodle 3.1 2371 */ 2372 public function grade_tree($courseid, $fillers=true, $category_grade_last=false, 2373 $collapsed=null, $nooutcomes=false) { 2374 debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER); 2375 self::__construct($courseid, $fillers, $category_grade_last, $collapsed, $nooutcomes); 2376 } 2377 2378 /** 2379 * Static recursive helper - removes items from collapsed categories 2380 * 2381 * @param array &$element The seed of the recursion 2382 * @param array $collapsed array of collapsed categories 2383 * 2384 * @return void 2385 */ 2386 public function category_collapse(&$element, $collapsed) { 2387 if ($element['type'] != 'category') { 2388 return; 2389 } 2390 if (empty($element['children']) or count($element['children']) < 2) { 2391 return; 2392 } 2393 2394 if (in_array($element['object']->id, $collapsed['aggregatesonly'])) { 2395 $category_item = reset($element['children']); //keep only category item 2396 $element['children'] = array(key($element['children'])=>$category_item); 2397 2398 } else { 2399 if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item 2400 reset($element['children']); 2401 $first_key = key($element['children']); 2402 unset($element['children'][$first_key]); 2403 } 2404 foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children 2405 grade_tree::category_collapse($element['children'][$sortorder], $collapsed); 2406 } 2407 } 2408 } 2409 2410 /** 2411 * Static recursive helper - removes all outcomes 2412 * 2413 * @param array &$element The seed of the recursion 2414 * 2415 * @return void 2416 */ 2417 public function no_outcomes(&$element) { 2418 if ($element['type'] != 'category') { 2419 return; 2420 } 2421 foreach ($element['children'] as $sortorder=>$child) { 2422 if ($element['children'][$sortorder]['type'] == 'item' 2423 and $element['children'][$sortorder]['object']->is_outcome_item()) { 2424 unset($element['children'][$sortorder]); 2425 2426 } else if ($element['children'][$sortorder]['type'] == 'category') { 2427 grade_tree::no_outcomes($element['children'][$sortorder]); 2428 } 2429 } 2430 } 2431 2432 /** 2433 * Static recursive helper - makes the grade_item for category the last children 2434 * 2435 * @param array &$element The seed of the recursion 2436 * 2437 * @return void 2438 */ 2439 public function category_grade_last(&$element) { 2440 if (empty($element['children'])) { 2441 return; 2442 } 2443 if (count($element['children']) < 2) { 2444 return; 2445 } 2446 $first_item = reset($element['children']); 2447 if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') { 2448 // the category item might have been already removed 2449 $order = key($element['children']); 2450 unset($element['children'][$order]); 2451 $element['children'][$order] =& $first_item; 2452 } 2453 foreach ($element['children'] as $sortorder => $child) { 2454 grade_tree::category_grade_last($element['children'][$sortorder]); 2455 } 2456 } 2457 2458 /** 2459 * Static recursive helper - fills the levels array, useful when accessing tree elements of one level 2460 * 2461 * @param array &$levels The levels of the grade tree through which to recurse 2462 * @param array &$element The seed of the recursion 2463 * @param int $depth How deep are we? 2464 * @return void 2465 */ 2466 public function fill_levels(&$levels, &$element, $depth) { 2467 if (!array_key_exists($depth, $levels)) { 2468 $levels[$depth] = array(); 2469 } 2470 2471 // prepare unique identifier 2472 if ($element['type'] == 'category') { 2473 $element['eid'] = 'cg'.$element['object']->id; 2474 } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) { 2475 $element['eid'] = 'ig'.$element['object']->id; 2476 $this->items[$element['object']->id] =& $element['object']; 2477 } 2478 2479 $levels[$depth][] =& $element; 2480 $depth++; 2481 if (empty($element['children'])) { 2482 return; 2483 } 2484 $prev = 0; 2485 foreach ($element['children'] as $sortorder=>$child) { 2486 grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth); 2487 $element['children'][$sortorder]['prev'] = $prev; 2488 $element['children'][$sortorder]['next'] = 0; 2489 if ($prev) { 2490 $element['children'][$prev]['next'] = $sortorder; 2491 } 2492 $prev = $sortorder; 2493 } 2494 } 2495 2496 /** 2497 * Determines whether the grade tree item can be displayed. 2498 * This is particularly targeted for grade categories that have no total (None) when rendering the grade tree. 2499 * It checks if the grade tree item is of type 'category', and makes sure that the category, or at least one of children, 2500 * can be output. 2501 * 2502 * @param array $element The grade category element. 2503 * @return bool True if the grade tree item can be displayed. False, otherwise. 2504 */ 2505 public static function can_output_item($element) { 2506 $canoutput = true; 2507 2508 if ($element['type'] === 'category') { 2509 $object = $element['object']; 2510 $category = grade_category::fetch(array('id' => $object->id)); 2511 // Category has total, we can output this. 2512 if ($category->get_grade_item()->gradetype != GRADE_TYPE_NONE) { 2513 return true; 2514 } 2515 2516 // Category has no total and has no children, no need to output this. 2517 if (empty($element['children'])) { 2518 return false; 2519 } 2520 2521 $canoutput = false; 2522 // Loop over children and make sure at least one child can be output. 2523 foreach ($element['children'] as $child) { 2524 $canoutput = self::can_output_item($child); 2525 if ($canoutput) { 2526 break; 2527 } 2528 } 2529 } 2530 2531 return $canoutput; 2532 } 2533 2534 /** 2535 * Static recursive helper - makes full tree (all leafes are at the same level) 2536 * 2537 * @param array &$element The seed of the recursion 2538 * @param int $depth How deep are we? 2539 * 2540 * @return int 2541 */ 2542 public function inject_fillers(&$element, $depth) { 2543 $depth++; 2544 2545 if (empty($element['children'])) { 2546 return $depth; 2547 } 2548 $chdepths = array(); 2549 $chids = array_keys($element['children']); 2550 $last_child = end($chids); 2551 $first_child = reset($chids); 2552 2553 foreach ($chids as $chid) { 2554 $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth); 2555 } 2556 arsort($chdepths); 2557 2558 $maxdepth = reset($chdepths); 2559 foreach ($chdepths as $chid=>$chd) { 2560 if ($chd == $maxdepth) { 2561 continue; 2562 } 2563 if (!self::can_output_item($element['children'][$chid])) { 2564 continue; 2565 } 2566 for ($i=0; $i < $maxdepth-$chd; $i++) { 2567 if ($chid == $first_child) { 2568 $type = 'fillerfirst'; 2569 } else if ($chid == $last_child) { 2570 $type = 'fillerlast'; 2571 } else { 2572 $type = 'filler'; 2573 } 2574 $oldchild =& $element['children'][$chid]; 2575 $element['children'][$chid] = array('object'=>'filler', 'type'=>$type, 2576 'eid'=>'', 'depth'=>$element['object']->depth, 2577 'children'=>array($oldchild)); 2578 } 2579 } 2580 2581 return $maxdepth; 2582 } 2583 2584 /** 2585 * Static recursive helper - add colspan information into categories 2586 * 2587 * @param array &$element The seed of the recursion 2588 * 2589 * @return int 2590 */ 2591 public function inject_colspans(&$element) { 2592 if (empty($element['children'])) { 2593 return 1; 2594 } 2595 $count = 0; 2596 foreach ($element['children'] as $key=>$child) { 2597 if (!self::can_output_item($child)) { 2598 continue; 2599 } 2600 $count += grade_tree::inject_colspans($element['children'][$key]); 2601 } 2602 $element['colspan'] = $count; 2603 return $count; 2604 } 2605 2606 /** 2607 * Parses the array in search of a given eid and returns a element object with 2608 * information about the element it has found. 2609 * @param int $eid Gradetree Element ID 2610 * @return object element 2611 */ 2612 public function locate_element($eid) { 2613 // it is a grade - construct a new object 2614 if (strpos($eid, 'n') === 0) { 2615 if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) { 2616 return null; 2617 } 2618 2619 $itemid = $matches[1]; 2620 $userid = $matches[2]; 2621 2622 //extra security check - the grade item must be in this tree 2623 if (!$item_el = $this->locate_element('ig'.$itemid)) { 2624 return null; 2625 } 2626 2627 // $gradea->id may be null - means does not exist yet 2628 $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid)); 2629 2630 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2631 return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade'); 2632 2633 } else if (strpos($eid, 'g') === 0) { 2634 $id = (int) substr($eid, 1); 2635 if (!$grade = grade_grade::fetch(array('id'=>$id))) { 2636 return null; 2637 } 2638 //extra security check - the grade item must be in this tree 2639 if (!$item_el = $this->locate_element('ig'.$grade->itemid)) { 2640 return null; 2641 } 2642 $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods! 2643 return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade'); 2644 } 2645 2646 // it is a category or item 2647 foreach ($this->levels as $row) { 2648 foreach ($row as $element) { 2649 if ($element['type'] == 'filler') { 2650 continue; 2651 } 2652 if ($element['eid'] == $eid) { 2653 return $element; 2654 } 2655 } 2656 } 2657 2658 return null; 2659 } 2660 2661 /** 2662 * Returns a well-formed XML representation of the grade-tree using recursion. 2663 * 2664 * @param array $root The current element in the recursion. If null, starts at the top of the tree. 2665 * @param string $tabs The control character to use for tabs 2666 * 2667 * @return string $xml 2668 */ 2669 public function exporttoxml($root=null, $tabs="\t") { 2670 $xml = null; 2671 $first = false; 2672 if (is_null($root)) { 2673 $root = $this->top_element; 2674 $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n"; 2675 $xml .= "<gradetree>\n"; 2676 $first = true; 2677 } 2678 2679 $type = 'undefined'; 2680 if (strpos($root['object']->table, 'grade_categories') !== false) { 2681 $type = 'category'; 2682 } else if (strpos($root['object']->table, 'grade_items') !== false) { 2683 $type = 'item'; 2684 } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { 2685 $type = 'outcome'; 2686 } 2687 2688 $xml .= "$tabs<element type=\"$type\">\n"; 2689 foreach ($root['object'] as $var => $value) { 2690 if (!is_object($value) && !is_array($value) && !empty($value)) { 2691 $xml .= "$tabs\t<$var>$value</$var>\n"; 2692 } 2693 } 2694 2695 if (!empty($root['children'])) { 2696 $xml .= "$tabs\t<children>\n"; 2697 foreach ($root['children'] as $sortorder => $child) { 2698 $xml .= $this->exportToXML($child, $tabs."\t\t"); 2699 } 2700 $xml .= "$tabs\t</children>\n"; 2701 } 2702 2703 $xml .= "$tabs</element>\n"; 2704 2705 if ($first) { 2706 $xml .= "</gradetree>"; 2707 } 2708 2709 return $xml; 2710 } 2711 2712 /** 2713 * Returns a JSON representation of the grade-tree using recursion. 2714 * 2715 * @param array $root The current element in the recursion. If null, starts at the top of the tree. 2716 * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy 2717 * 2718 * @return string 2719 */ 2720 public function exporttojson($root=null, $tabs="\t") { 2721 $json = null; 2722 $first = false; 2723 if (is_null($root)) { 2724 $root = $this->top_element; 2725 $first = true; 2726 } 2727 2728 $name = ''; 2729 2730 2731 if (strpos($root['object']->table, 'grade_categories') !== false) { 2732 $name = $root['object']->fullname; 2733 if ($name == '?') { 2734 $name = $root['object']->get_name(); 2735 } 2736 } else if (strpos($root['object']->table, 'grade_items') !== false) { 2737 $name = $root['object']->itemname; 2738 } else if (strpos($root['object']->table, 'grade_outcomes') !== false) { 2739 $name = $root['object']->itemname; 2740 } 2741 2742 $json .= "$tabs {\n"; 2743 $json .= "$tabs\t \"type\": \"{$root['type']}\",\n"; 2744 $json .= "$tabs\t \"name\": \"$name\",\n"; 2745 2746 foreach ($root['object'] as $var => $value) { 2747 if (!is_object($value) && !is_array($value) && !empty($value)) { 2748 $json .= "$tabs\t \"$var\": \"$value\",\n"; 2749 } 2750 } 2751 2752 $json = substr($json, 0, strrpos($json, ',')); 2753 2754 if (!empty($root['children'])) { 2755 $json .= ",\n$tabs\t\"children\": [\n"; 2756 foreach ($root['children'] as $sortorder => $child) { 2757 $json .= $this->exportToJSON($child, $tabs."\t\t"); 2758 } 2759 $json = substr($json, 0, strrpos($json, ',')); 2760 $json .= "\n$tabs\t]\n"; 2761 } 2762 2763 if ($first) { 2764 $json .= "\n}"; 2765 } else { 2766 $json .= "\n$tabs},\n"; 2767 } 2768 2769 return $json; 2770 } 2771 2772 /** 2773 * Returns the array of levels 2774 * 2775 * @return array 2776 */ 2777 public function get_levels() { 2778 return $this->levels; 2779 } 2780 2781 /** 2782 * Returns the array of grade items 2783 * 2784 * @return array 2785 */ 2786 public function get_items() { 2787 return $this->items; 2788 } 2789 2790 /** 2791 * Returns a specific Grade Item 2792 * 2793 * @param int $itemid The ID of the grade_item object 2794 * 2795 * @return grade_item 2796 */ 2797 public function get_item($itemid) { 2798 if (array_key_exists($itemid, $this->items)) { 2799 return $this->items[$itemid]; 2800 } else { 2801 return false; 2802 } 2803 } 2804 } 2805 2806 /** 2807 * Local shortcut function for creating an edit/delete button for a grade_* object. 2808 * @param string $type 'edit' or 'delete' 2809 * @param int $courseid The Course ID 2810 * @param grade_* $object The grade_* object 2811 * @return string html 2812 */ 2813 function grade_button($type, $courseid, $object) { 2814 global $CFG, $OUTPUT; 2815 if (preg_match('/grade_(.*)/', get_class($object), $matches)) { 2816 $objectidstring = $matches[1] . 'id'; 2817 } else { 2818 throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!'); 2819 } 2820 2821 $strdelete = get_string('delete'); 2822 $stredit = get_string('edit'); 2823 2824 if ($type == 'delete') { 2825 $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey())); 2826 } else if ($type == 'edit') { 2827 $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id)); 2828 } 2829 2830 return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}, '', array('class' => 'iconsmall'))); 2831 2832 } 2833 2834 /** 2835 * This method adds settings to the settings block for the grade system and its 2836 * plugins 2837 * 2838 * @global moodle_page $PAGE 2839 */ 2840 function grade_extend_settings($plugininfo, $courseid) { 2841 global $PAGE; 2842 2843 $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER, 2844 null, 'gradeadmin'); 2845 2846 $strings = array_shift($plugininfo); 2847 2848 if ($reports = grade_helper::get_plugins_reports($courseid)) { 2849 foreach ($reports as $report) { 2850 $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', '')); 2851 } 2852 } 2853 2854 if ($settings = grade_helper::get_info_manage_settings($courseid)) { 2855 $settingsnode = $gradenode->add($strings['settings'], null, navigation_node::TYPE_CONTAINER); 2856 foreach ($settings as $setting) { 2857 $settingsnode->add($setting->string, $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', '')); 2858 } 2859 } 2860 2861 if ($imports = grade_helper::get_plugins_import($courseid)) { 2862 $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER); 2863 foreach ($imports as $import) { 2864 $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', '')); 2865 } 2866 } 2867 2868 if ($exports = grade_helper::get_plugins_export($courseid)) { 2869 $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER); 2870 foreach ($exports as $export) { 2871 $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', '')); 2872 } 2873 } 2874 2875 if ($letters = grade_helper::get_info_letters($courseid)) { 2876 $letters = array_shift($letters); 2877 $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', '')); 2878 } 2879 2880 if ($outcomes = grade_helper::get_info_outcomes($courseid)) { 2881 $outcomes = array_shift($outcomes); 2882 $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', '')); 2883 } 2884 2885 if ($scales = grade_helper::get_info_scales($courseid)) { 2886 $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', '')); 2887 } 2888 2889 if ($gradenode->contains_active_node()) { 2890 // If the gradenode is active include the settings base node (gradeadministration) in 2891 // the navbar, typcially this is ignored. 2892 $PAGE->navbar->includesettingsbase = true; 2893 2894 // If we can get the course admin node make sure it is closed by default 2895 // as in this case the gradenode will be opened 2896 if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){ 2897 $coursenode->make_inactive(); 2898 $coursenode->forceopen = false; 2899 } 2900 } 2901 } 2902 2903 /** 2904 * Grade helper class 2905 * 2906 * This class provides several helpful functions that work irrespective of any 2907 * current state. 2908 * 2909 * @copyright 2010 Sam Hemelryk 2910 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2911 */ 2912 abstract class grade_helper { 2913 /** 2914 * Cached manage settings info {@see get_info_settings} 2915 * @var grade_plugin_info|false 2916 */ 2917 protected static $managesetting = null; 2918 /** 2919 * Cached grade report plugins {@see get_plugins_reports} 2920 * @var array|false 2921 */ 2922 protected static $gradereports = null; 2923 /** 2924 * Cached grade report plugins preferences {@see get_info_scales} 2925 * @var array|false 2926 */ 2927 protected static $gradereportpreferences = null; 2928 /** 2929 * Cached scale info {@see get_info_scales} 2930 * @var grade_plugin_info|false 2931 */ 2932 protected static $scaleinfo = null; 2933 /** 2934 * Cached outcome info {@see get_info_outcomes} 2935 * @var grade_plugin_info|false 2936 */ 2937 protected static $outcomeinfo = null; 2938 /** 2939 * Cached leftter info {@see get_info_letters} 2940 * @var grade_plugin_info|false 2941 */ 2942 protected static $letterinfo = null; 2943 /** 2944 * Cached grade import plugins {@see get_plugins_import} 2945 * @var array|false 2946 */ 2947 protected static $importplugins = null; 2948 /** 2949 * Cached grade export plugins {@see get_plugins_export} 2950 * @var array|false 2951 */ 2952 protected static $exportplugins = null; 2953 /** 2954 * Cached grade plugin strings 2955 * @var array 2956 */ 2957 protected static $pluginstrings = null; 2958 /** 2959 * Cached grade aggregation strings 2960 * @var array 2961 */ 2962 protected static $aggregationstrings = null; 2963 2964 /** 2965 * Gets strings commonly used by the describe plugins 2966 * 2967 * report => get_string('view'), 2968 * scale => get_string('scales'), 2969 * outcome => get_string('outcomes', 'grades'), 2970 * letter => get_string('letters', 'grades'), 2971 * export => get_string('export', 'grades'), 2972 * import => get_string('import'), 2973 * settings => get_string('settings') 2974 * 2975 * @return array 2976 */ 2977 public static function get_plugin_strings() { 2978 if (self::$pluginstrings === null) { 2979 self::$pluginstrings = array( 2980 'report' => get_string('view'), 2981 'scale' => get_string('scales'), 2982 'outcome' => get_string('outcomes', 'grades'), 2983 'letter' => get_string('letters', 'grades'), 2984 'export' => get_string('export', 'grades'), 2985 'import' => get_string('import'), 2986 'settings' => get_string('edittree', 'grades') 2987 ); 2988 } 2989 return self::$pluginstrings; 2990 } 2991 2992 /** 2993 * Gets strings describing the available aggregation methods. 2994 * 2995 * @return array 2996 */ 2997 public static function get_aggregation_strings() { 2998 if (self::$aggregationstrings === null) { 2999 self::$aggregationstrings = array( 3000 GRADE_AGGREGATE_MEAN => get_string('aggregatemean', 'grades'), 3001 GRADE_AGGREGATE_WEIGHTED_MEAN => get_string('aggregateweightedmean', 'grades'), 3002 GRADE_AGGREGATE_WEIGHTED_MEAN2 => get_string('aggregateweightedmean2', 'grades'), 3003 GRADE_AGGREGATE_EXTRACREDIT_MEAN => get_string('aggregateextracreditmean', 'grades'), 3004 GRADE_AGGREGATE_MEDIAN => get_string('aggregatemedian', 'grades'), 3005 GRADE_AGGREGATE_MIN => get_string('aggregatemin', 'grades'), 3006 GRADE_AGGREGATE_MAX => get_string('aggregatemax', 'grades'), 3007 GRADE_AGGREGATE_MODE => get_string('aggregatemode', 'grades'), 3008 GRADE_AGGREGATE_SUM => get_string('aggregatesum', 'grades') 3009 ); 3010 } 3011 return self::$aggregationstrings; 3012 } 3013 3014 /** 3015 * Get grade_plugin_info object for managing settings if the user can 3016 * 3017 * @param int $courseid 3018 * @return grade_plugin_info[] 3019 */ 3020 public static function get_info_manage_settings($courseid) { 3021 if (self::$managesetting !== null) { 3022 return self::$managesetting; 3023 } 3024 $context = context_course::instance($courseid); 3025 self::$managesetting = array(); 3026 if ($courseid != SITEID && has_capability('moodle/grade:manage', $context)) { 3027 self::$managesetting['gradebooksetup'] = new grade_plugin_info('setup', 3028 new moodle_url('/grade/edit/tree/index.php', array('id' => $courseid)), 3029 get_string('gradebooksetup', 'grades')); 3030 self::$managesetting['coursesettings'] = new grade_plugin_info('coursesettings', 3031 new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), 3032 get_string('coursegradesettings', 'grades')); 3033 } 3034 if (self::$gradereportpreferences === null) { 3035 self::get_plugins_reports($courseid); 3036 } 3037 if (self::$gradereportpreferences) { 3038 self::$managesetting = array_merge(self::$managesetting, self::$gradereportpreferences); 3039 } 3040 return self::$managesetting; 3041 } 3042 /** 3043 * Returns an array of plugin reports as grade_plugin_info objects 3044 * 3045 * @param int $courseid 3046 * @return array 3047 */ 3048 public static function get_plugins_reports($courseid) { 3049 global $SITE, $CFG; 3050 3051 if (self::$gradereports !== null) { 3052 return self::$gradereports; 3053 } 3054 $context = context_course::instance($courseid); 3055 $gradereports = array(); 3056 $gradepreferences = array(); 3057 foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) { 3058 //some reports make no sense if we're not within a course 3059 if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) { 3060 continue; 3061 } 3062 3063 // Remove outcomes report if outcomes not enabled. 3064 if ($plugin === 'outcomes' && empty($CFG->enableoutcomes)) { 3065 continue; 3066 } 3067 3068 // Remove ones we can't see 3069 if (!has_capability('gradereport/'.$plugin.':view', $context)) { 3070 continue; 3071 } 3072 3073 // Singleview doesn't doesn't accomodate for all cap combos yet, so this is hardcoded.. 3074 if ($plugin === 'singleview' && !has_all_capabilities(array('moodle/grade:viewall', 3075 'moodle/grade:edit'), $context)) { 3076 continue; 3077 } 3078 3079 $pluginstr = get_string('pluginname', 'gradereport_'.$plugin); 3080 $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid)); 3081 $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3082 3083 // Add link to preferences tab if such a page exists 3084 if (file_exists($plugindir.'/preferences.php')) { 3085 $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id' => $courseid)); 3086 $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, 3087 get_string('preferences', 'grades') . ': ' . $pluginstr); 3088 } 3089 } 3090 if (count($gradereports) == 0) { 3091 $gradereports = false; 3092 $gradepreferences = false; 3093 } else if (count($gradepreferences) == 0) { 3094 $gradepreferences = false; 3095 asort($gradereports); 3096 } else { 3097 asort($gradereports); 3098 asort($gradepreferences); 3099 } 3100 self::$gradereports = $gradereports; 3101 self::$gradereportpreferences = $gradepreferences; 3102 return self::$gradereports; 3103 } 3104 3105 /** 3106 * Get information on scales 3107 * @param int $courseid 3108 * @return grade_plugin_info 3109 */ 3110 public static function get_info_scales($courseid) { 3111 if (self::$scaleinfo !== null) { 3112 return self::$scaleinfo; 3113 } 3114 if (has_capability('moodle/course:managescales', context_course::instance($courseid))) { 3115 $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid)); 3116 self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view')); 3117 } else { 3118 self::$scaleinfo = false; 3119 } 3120 return self::$scaleinfo; 3121 } 3122 /** 3123 * Get information on outcomes 3124 * @param int $courseid 3125 * @return grade_plugin_info 3126 */ 3127 public static function get_info_outcomes($courseid) { 3128 global $CFG, $SITE; 3129 3130 if (self::$outcomeinfo !== null) { 3131 return self::$outcomeinfo; 3132 } 3133 $context = context_course::instance($courseid); 3134 $canmanage = has_capability('moodle/grade:manage', $context); 3135 $canupdate = has_capability('moodle/course:update', $context); 3136 if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) { 3137 $outcomes = array(); 3138 if ($canupdate) { 3139 if ($courseid!=$SITE->id) { 3140 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); 3141 $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades')); 3142 } 3143 $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid)); 3144 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades')); 3145 $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid)); 3146 $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades')); 3147 } else { 3148 if ($courseid!=$SITE->id) { 3149 $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid)); 3150 $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades')); 3151 } 3152 } 3153 self::$outcomeinfo = $outcomes; 3154 } else { 3155 self::$outcomeinfo = false; 3156 } 3157 return self::$outcomeinfo; 3158 } 3159 /** 3160 * Get information on letters 3161 * @param int $courseid 3162 * @return array 3163 */ 3164 public static function get_info_letters($courseid) { 3165 global $SITE; 3166 if (self::$letterinfo !== null) { 3167 return self::$letterinfo; 3168 } 3169 $context = context_course::instance($courseid); 3170 $canmanage = has_capability('moodle/grade:manage', $context); 3171 $canmanageletters = has_capability('moodle/grade:manageletters', $context); 3172 if ($canmanage || $canmanageletters) { 3173 // Redirect to system context when report is accessed from admin settings MDL-31633 3174 if ($context->instanceid == $SITE->id) { 3175 $param = array('edit' => 1); 3176 } else { 3177 $param = array('edit' => 1,'id' => $context->id); 3178 } 3179 self::$letterinfo = array( 3180 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')), 3181 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', $param), get_string('edit')) 3182 ); 3183 } else { 3184 self::$letterinfo = false; 3185 } 3186 return self::$letterinfo; 3187 } 3188 /** 3189 * Get information import plugins 3190 * @param int $courseid 3191 * @return array 3192 */ 3193 public static function get_plugins_import($courseid) { 3194 global $CFG; 3195 3196 if (self::$importplugins !== null) { 3197 return self::$importplugins; 3198 } 3199 $importplugins = array(); 3200 $context = context_course::instance($courseid); 3201 3202 if (has_capability('moodle/grade:import', $context)) { 3203 foreach (core_component::get_plugin_list('gradeimport') as $plugin => $plugindir) { 3204 if (!has_capability('gradeimport/'.$plugin.':view', $context)) { 3205 continue; 3206 } 3207 $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin); 3208 $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid)); 3209 $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3210 } 3211 3212 // Show key manager if grade publishing is enabled and the user has xml publishing capability. 3213 // XML is the only grade import plugin that has publishing feature. 3214 if ($CFG->gradepublishing && has_capability('gradeimport/xml:publish', $context)) { 3215 $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid)); 3216 $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); 3217 } 3218 } 3219 3220 if (count($importplugins) > 0) { 3221 asort($importplugins); 3222 self::$importplugins = $importplugins; 3223 } else { 3224 self::$importplugins = false; 3225 } 3226 return self::$importplugins; 3227 } 3228 /** 3229 * Get information export plugins 3230 * @param int $courseid 3231 * @return array 3232 */ 3233 public static function get_plugins_export($courseid) { 3234 global $CFG; 3235 3236 if (self::$exportplugins !== null) { 3237 return self::$exportplugins; 3238 } 3239 $context = context_course::instance($courseid); 3240 $exportplugins = array(); 3241 $canpublishgrades = 0; 3242 if (has_capability('moodle/grade:export', $context)) { 3243 foreach (core_component::get_plugin_list('gradeexport') as $plugin => $plugindir) { 3244 if (!has_capability('gradeexport/'.$plugin.':view', $context)) { 3245 continue; 3246 } 3247 // All the grade export plugins has grade publishing capabilities. 3248 if (has_capability('gradeexport/'.$plugin.':publish', $context)) { 3249 $canpublishgrades++; 3250 } 3251 3252 $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin); 3253 $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid)); 3254 $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr); 3255 } 3256 3257 // Show key manager if grade publishing is enabled and the user has at least one grade publishing capability. 3258 if ($CFG->gradepublishing && $canpublishgrades != 0) { 3259 $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid)); 3260 $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades')); 3261 } 3262 } 3263 if (count($exportplugins) > 0) { 3264 asort($exportplugins); 3265 self::$exportplugins = $exportplugins; 3266 } else { 3267 self::$exportplugins = false; 3268 } 3269 return self::$exportplugins; 3270 } 3271 3272 /** 3273 * Returns the value of a field from a user record 3274 * 3275 * @param stdClass $user object 3276 * @param stdClass $field object 3277 * @return string value of the field 3278 */ 3279 public static function get_user_field_value($user, $field) { 3280 if (!empty($field->customid)) { 3281 $fieldname = 'customfield_' . $field->customid; 3282 if (!empty($user->{$fieldname}) || is_numeric($user->{$fieldname})) { 3283 $fieldvalue = $user->{$fieldname}; 3284 } else { 3285 $fieldvalue = $field->default; 3286 } 3287 } else { 3288 $fieldvalue = $user->{$field->shortname}; 3289 } 3290 return $fieldvalue; 3291 } 3292 3293 /** 3294 * Returns an array of user profile fields to be included in export 3295 * 3296 * @param int $courseid 3297 * @param bool $includecustomfields 3298 * @return array An array of stdClass instances with customid, shortname, datatype, default and fullname fields 3299 */ 3300 public static function get_user_profile_fields($courseid, $includecustomfields = false) { 3301 global $CFG, $DB; 3302 3303 // Gets the fields that have to be hidden 3304 $hiddenfields = array_map('trim', explode(',', $CFG->hiddenuserfields)); 3305 $context = context_course::instance($courseid); 3306 $canseehiddenfields = has_capability('moodle/course:viewhiddenuserfields', $context); 3307 if ($canseehiddenfields) { 3308 $hiddenfields = array(); 3309 } 3310 3311 $fields = array(); 3312 require_once($CFG->dirroot.'/user/lib.php'); // Loads user_get_default_fields() 3313 require_once($CFG->dirroot.'/user/profile/lib.php'); // Loads constants, such as PROFILE_VISIBLE_ALL 3314 $userdefaultfields = user_get_default_fields(); 3315 3316 // Sets the list of profile fields 3317 $userprofilefields = array_map('trim', explode(',', $CFG->grade_export_userprofilefields)); 3318 if (!empty($userprofilefields)) { 3319 foreach ($userprofilefields as $field) { 3320 $field = trim($field); 3321 if (in_array($field, $hiddenfields) || !in_array($field, $userdefaultfields)) { 3322 continue; 3323 } 3324 $obj = new stdClass(); 3325 $obj->customid = 0; 3326 $obj->shortname = $field; 3327 $obj->fullname = get_string($field); 3328 $fields[] = $obj; 3329 } 3330 } 3331 3332 // Sets the list of custom profile fields 3333 $customprofilefields = array_map('trim', explode(',', $CFG->grade_export_customprofilefields)); 3334 if ($includecustomfields && !empty($customprofilefields)) { 3335 $customfields = profile_get_user_fields_with_data(0); 3336 3337 foreach ($customfields as $fieldobj) { 3338 $field = (object)$fieldobj->get_field_config_for_external(); 3339 // Make sure we can display this custom field 3340 if (!in_array($field->shortname, $customprofilefields)) { 3341 continue; 3342 } else if (in_array($field->shortname, $hiddenfields)) { 3343 continue; 3344 } else if ($field->visible != PROFILE_VISIBLE_ALL && !$canseehiddenfields) { 3345 continue; 3346 } 3347 3348 $obj = new stdClass(); 3349 $obj->customid = $field->id; 3350 $obj->shortname = $field->shortname; 3351 $obj->fullname = format_string($field->name); 3352 $obj->datatype = $field->datatype; 3353 $obj->default = $field->defaultdata; 3354 $fields[] = $obj; 3355 } 3356 } 3357 3358 return $fields; 3359 } 3360 3361 /** 3362 * This helper method gets a snapshot of all the weights for a course. 3363 * It is used as a quick method to see if any wieghts have been automatically adjusted. 3364 * @param int $courseid 3365 * @return array of itemid -> aggregationcoef2 3366 */ 3367 public static function fetch_all_natural_weights_for_course($courseid) { 3368 global $DB; 3369 $result = array(); 3370 3371 $records = $DB->get_records('grade_items', array('courseid'=>$courseid), 'id', 'id, aggregationcoef2'); 3372 foreach ($records as $record) { 3373 $result[$record->id] = $record->aggregationcoef2; 3374 } 3375 return $result; 3376 } 3377 3378 /** 3379 * Resets all static caches. 3380 * 3381 * @return void 3382 */ 3383 public static function reset_caches() { 3384 self::$managesetting = null; 3385 self::$gradereports = null; 3386 self::$gradereportpreferences = null; 3387 self::$scaleinfo = null; 3388 self::$outcomeinfo = null; 3389 self::$letterinfo = null; 3390 self::$importplugins = null; 3391 self::$exportplugins = null; 3392 self::$pluginstrings = null; 3393 self::$aggregationstrings = null; 3394 } 3395 } 3396
title
Description
Body
title
Description
Body
title
Description
Body
title
Body