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 * File containing the grade_report class 19 * 20 * @package core_grades 21 * @copyright 2007 Moodle Pty Ltd (http://moodle.com) 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 require_once($CFG->libdir.'/gradelib.php'); 26 27 /** 28 * An abstract class containing variables and methods used by all or most reports. 29 * @copyright 2007 Moodle Pty Ltd (http://moodle.com) 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 abstract class grade_report { 33 /** 34 * The courseid. 35 * @var int $courseid 36 */ 37 public $courseid; 38 39 /** 40 * The course. 41 * @var object $course 42 */ 43 public $course; 44 45 /** Grade plugin return tracking object. 46 * @var object $gpr 47 */ 48 public $gpr; 49 50 /** 51 * The context. 52 * 53 * @var context $context 54 */ 55 public $context; 56 57 /** 58 * The grade_tree object. 59 * @var grade_tree $gtree 60 */ 61 public $gtree; 62 63 /** 64 * User preferences related to this report. 65 * @var array $prefs 66 */ 67 public $prefs = array(); 68 69 /** 70 * The roles for this report. 71 * @var string $gradebookroles 72 */ 73 public $gradebookroles; 74 75 /** 76 * base url for sorting by first/last name. 77 * @var string $baseurl 78 */ 79 public $baseurl; 80 81 /** 82 * base url for paging. 83 * @var string $pbarurl 84 */ 85 public $pbarurl; 86 87 /** 88 * Current page (for paging). 89 * @var int $page 90 */ 91 public $page; 92 93 /** 94 * Array of cached language strings (using get_string() all the time takes a long time!). 95 * @var array $lang_strings 96 */ 97 public $lang_strings = array(); 98 99 // GROUP VARIABLES (including SQL) 100 101 /** 102 * The current group being displayed. 103 * @var int $currentgroup 104 */ 105 public $currentgroup; 106 107 /** 108 * The current groupname being displayed. 109 * @var string $currentgroupname 110 */ 111 public $currentgroupname; 112 113 /** 114 * Current course group mode 115 * @var int $groupmode 116 */ 117 public $groupmode; 118 119 /** 120 * A HTML select element used to select the current group. 121 * @var string $group_selector 122 */ 123 public $group_selector; 124 125 /** 126 * An SQL fragment used to add linking information to the group tables. 127 * @var string $groupsql 128 */ 129 protected $groupsql; 130 131 /** 132 * An SQL constraint to append to the queries used by this object to build the report. 133 * @var string $groupwheresql 134 */ 135 protected $groupwheresql; 136 137 /** 138 * The ordered params for $groupwheresql 139 * @var array $groupwheresql_params 140 */ 141 protected $groupwheresql_params = array(); 142 143 // USER VARIABLES (including SQL). 144 145 /** 146 * An SQL constraint to append to the queries used by this object to build the report. 147 * @var string $userwheresql 148 */ 149 protected $userwheresql; 150 151 /** 152 * The ordered params for $userwheresql 153 * @var array $userwheresql_params 154 */ 155 protected $userwheresql_params = array(); 156 157 /** 158 * Constructor. Sets local copies of user preferences and initialises grade_tree. 159 * @param int $courseid 160 * @param object $gpr grade plugin return tracking object 161 * @param string $context 162 * @param int $page The current page being viewed (when report is paged) 163 */ 164 public function __construct($courseid, $gpr, $context, $page=null) { 165 global $CFG, $COURSE, $DB; 166 167 if (empty($CFG->gradebookroles)) { 168 throw new \moodle_exception('norolesdefined', 'grades'); 169 } 170 171 $this->courseid = $courseid; 172 if ($this->courseid == $COURSE->id) { 173 $this->course = $COURSE; 174 } else { 175 $this->course = $DB->get_record('course', array('id' => $this->courseid)); 176 } 177 178 $this->gpr = $gpr; 179 $this->context = $context; 180 $this->page = $page; 181 182 // roles to be displayed in the gradebook 183 $this->gradebookroles = $CFG->gradebookroles; 184 185 // Set up link to preferences page 186 $this->preferences_page = $CFG->wwwroot.'/grade/report/grader/preferences.php?id='.$courseid; 187 188 // init gtree in child class 189 } 190 191 /** 192 * Given the name of a user preference (without grade_report_ prefix), locally saves then returns 193 * the value of that preference. If the preference has already been fetched before, 194 * the saved value is returned. If the preference is not set at the User level, the $CFG equivalent 195 * is given (site default). 196 * Can be called statically, but then doesn't benefit from caching 197 * @param string $pref The name of the preference (do not include the grade_report_ prefix) 198 * @param int $objectid An optional itemid or categoryid to check for a more fine-grained preference 199 * @return mixed The value of the preference 200 */ 201 public function get_pref($pref, $objectid=null) { 202 global $CFG; 203 $fullprefname = 'grade_report_' . $pref; 204 $shortprefname = 'grade_' . $pref; 205 206 $retval = null; 207 208 if (!isset($this) OR get_class($this) != 'grade_report') { 209 if (!empty($objectid)) { 210 $retval = get_user_preferences($fullprefname . $objectid, self::get_pref($pref)); 211 } else if (isset($CFG->$fullprefname)) { 212 $retval = get_user_preferences($fullprefname, $CFG->$fullprefname); 213 } else if (isset($CFG->$shortprefname)) { 214 $retval = get_user_preferences($fullprefname, $CFG->$shortprefname); 215 } else { 216 $retval = null; 217 } 218 } else { 219 if (empty($this->prefs[$pref.$objectid])) { 220 221 if (!empty($objectid)) { 222 $retval = get_user_preferences($fullprefname . $objectid); 223 if (empty($retval)) { 224 // No item pref found, we are returning the global preference 225 $retval = $this->get_pref($pref); 226 $objectid = null; 227 } 228 } else { 229 $retval = get_user_preferences($fullprefname, $CFG->$fullprefname); 230 } 231 $this->prefs[$pref.$objectid] = $retval; 232 } else { 233 $retval = $this->prefs[$pref.$objectid]; 234 } 235 } 236 237 return $retval; 238 } 239 240 /** 241 * Uses set_user_preferences() to update the value of a user preference. If 'default' is given as the value, 242 * the preference will be removed in favour of a higher-level preference. 243 * @param string $pref The name of the preference. 244 * @param mixed $pref_value The value of the preference. 245 * @param int $itemid An optional itemid to which the preference will be assigned 246 * @return bool Success or failure. 247 */ 248 public function set_pref($pref, $pref_value='default', $itemid=null) { 249 $fullprefname = 'grade_report_' . $pref; 250 if ($pref_value == 'default') { 251 return unset_user_preference($fullprefname.$itemid); 252 } else { 253 return set_user_preference($fullprefname.$itemid, $pref_value); 254 } 255 } 256 257 /** 258 * Handles form data sent by this report for this report. Abstract method to implement in all children. 259 * @abstract 260 * @param array $data 261 * @return mixed True or array of errors 262 */ 263 abstract public function process_data($data); 264 265 /** 266 * Processes a single action against a category, grade_item or grade. 267 * @param string $target Sortorder 268 * @param string $action Which action to take (edit, delete etc...) 269 * @return 270 */ 271 abstract public function process_action($target, $action); 272 273 /** 274 * First checks the cached language strings, then returns match if found, or uses get_string() 275 * to get it from the DB, caches it then returns it. 276 * @param string $strcode 277 * @param string $section Optional language section 278 * @return string 279 */ 280 public function get_lang_string($strcode, $section=null) { 281 if (empty($this->lang_strings[$strcode])) { 282 $this->lang_strings[$strcode] = get_string($strcode, $section); 283 } 284 return $this->lang_strings[$strcode]; 285 } 286 287 /** 288 * Fetches and returns a count of all the users that will be shown on this page. 289 * @param boolean $groups include groups limit 290 * @param boolean $users include users limit - default false, used for searching purposes 291 * @return int Count of users 292 */ 293 public function get_numusers($groups = true, $users = false) { 294 global $CFG, $DB; 295 $userwheresql = ""; 296 $groupsql = ""; 297 $groupwheresql = ""; 298 299 // Limit to users with a gradeable role. 300 list($gradebookrolessql, $gradebookrolesparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0'); 301 302 // Limit to users with an active enrollment. 303 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context); 304 305 // We want to query both the current context and parent contexts. 306 list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); 307 308 $params = array_merge($gradebookrolesparams, $enrolledparams, $relatedctxparams); 309 310 if ($users) { 311 $userwheresql = $this->userwheresql; 312 $params = array_merge($params, $this->userwheresql_params); 313 } 314 315 if ($groups) { 316 $groupsql = $this->groupsql; 317 $groupwheresql = $this->groupwheresql; 318 $params = array_merge($params, $this->groupwheresql_params); 319 } 320 321 $sql = "SELECT DISTINCT u.id 322 FROM {user} u 323 JOIN ($enrolledsql) je 324 ON je.id = u.id 325 JOIN {role_assignments} ra 326 ON u.id = ra.userid 327 $groupsql 328 WHERE ra.roleid $gradebookrolessql 329 AND u.deleted = 0 330 $userwheresql 331 $groupwheresql 332 AND ra.contextid $relatedctxsql"; 333 $selectedusers = $DB->get_records_sql($sql, $params); 334 335 $count = 0; 336 // Check if user's enrolment is active and should be displayed. 337 if (!empty($selectedusers)) { 338 $coursecontext = $this->context->get_course_context(true); 339 340 $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); 341 $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); 342 $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext); 343 344 if ($showonlyactiveenrol) { 345 $useractiveenrolments = get_enrolled_users($coursecontext, '', 0, 'u.id', null, 0, 0, true); 346 } 347 348 foreach ($selectedusers as $id => $value) { 349 if (!$showonlyactiveenrol || ($showonlyactiveenrol && array_key_exists($id, $useractiveenrolments))) { 350 $count++; 351 } 352 } 353 } 354 return $count; 355 } 356 357 /** 358 * Shows support for being used as a 'Grades' report. 359 */ 360 public static function supports_mygrades() { 361 return false; 362 } 363 364 /** 365 * Sets up this object's group variables, mainly to restrict the selection of users to display. 366 */ 367 protected function setup_groups() { 368 // find out current groups mode 369 if ($this->groupmode = groups_get_course_groupmode($this->course)) { 370 if (empty($this->gpr->groupid)) { 371 $this->currentgroup = groups_get_course_group($this->course, true); 372 } else { 373 $this->currentgroup = $this->gpr->groupid; 374 } 375 $this->group_selector = groups_print_course_menu($this->course, $this->pbarurl, true); 376 377 if ($this->groupmode == SEPARATEGROUPS and !$this->currentgroup and !has_capability('moodle/site:accessallgroups', $this->context)) { 378 $this->currentgroup = -2; // means can not access any groups at all 379 } 380 if ($this->currentgroup) { 381 if ($group = groups_get_group($this->currentgroup)) { 382 $this->currentgroupname = $group->name; 383 } 384 $this->groupsql = " JOIN {groups_members} gm ON gm.userid = u.id "; 385 $this->groupwheresql = " AND gm.groupid = :gr_grpid "; 386 $this->groupwheresql_params = array('gr_grpid'=>$this->currentgroup); 387 } 388 } 389 } 390 391 /** 392 * Sets up this report's user criteria to restrict the selection of users to display. 393 */ 394 public function setup_users() { 395 global $SESSION, $DB; 396 397 $filterfirstnamekey = "filterfirstname-{$this->context->id}"; 398 $filtersurnamekey = "filtersurname-{$this->context->id}"; 399 400 $this->userwheresql = ""; 401 $this->userwheresql_params = array(); 402 if (!empty($SESSION->gradereport[$filterfirstnamekey])) { 403 $this->userwheresql .= ' AND '.$DB->sql_like('u.firstname', ':firstname', false, false); 404 $this->userwheresql_params['firstname'] = $SESSION->gradereport[$filterfirstnamekey] . '%'; 405 } 406 if (!empty($SESSION->gradereport[$filtersurnamekey])) { 407 $this->userwheresql .= ' AND '.$DB->sql_like('u.lastname', ':lastname', false, false); 408 $this->userwheresql_params['lastname'] = $SESSION->gradereport[$filtersurnamekey] . '%'; 409 } 410 } 411 412 /** 413 * Returns an arrow icon inside an <a> tag, for the purpose of sorting a column. 414 * @param string $direction 415 * @param moodle_url $sortlink 416 */ 417 protected function get_sort_arrow($direction='move', $sortlink=null) { 418 global $OUTPUT; 419 $pix = array('up' => 't/sort_desc', 'down' => 't/sort_asc', 'move' => 't/sort'); 420 $matrix = array('up' => 'desc', 'down' => 'asc', 'move' => 'asc'); 421 $strsort = $this->get_lang_string('sort' . $matrix[$direction]); 422 423 $arrow = $OUTPUT->pix_icon($pix[$direction], '', '', ['class' => 'sorticon']); 424 return html_writer::link($sortlink, $arrow, ['title' => $strsort, 'aria-label' => $strsort]); 425 } 426 427 /** 428 * Optionally blank out course/category totals if they contain any hidden items 429 * @param string $courseid the course id 430 * @param string $course_item an instance of grade_item 431 * @param string $finalgrade the grade for the course_item 432 * @return array[] containing values for 'grade', 'grademax', 'grademin', 'aggregationstatus' and 'aggregationweight' 433 */ 434 protected function blank_hidden_total_and_adjust_bounds($courseid, $course_item, $finalgrade) { 435 global $CFG, $DB; 436 static $hiding_affected = null;//array of items in this course affected by hiding 437 438 // If we're dealing with multiple users we need to know when we've moved on to a new user. 439 static $previous_userid = null; 440 441 // If we're dealing with multiple courses we need to know when we've moved on to a new course. 442 static $previous_courseid = null; 443 444 $coursegradegrade = grade_grade::fetch(array('userid'=>$this->user->id, 'itemid'=>$course_item->id)); 445 $grademin = $course_item->grademin; 446 $grademax = $course_item->grademax; 447 if ($coursegradegrade) { 448 $grademin = $coursegradegrade->get_grade_min(); 449 $grademax = $coursegradegrade->get_grade_max(); 450 } else { 451 $coursegradegrade = new grade_grade(array('userid'=>$this->user->id, 'itemid'=>$course_item->id), false); 452 } 453 $hint = $coursegradegrade->get_aggregation_hint(); 454 $aggregationstatus = $hint['status']; 455 $aggregationweight = $hint['weight']; 456 457 if (!is_array($this->showtotalsifcontainhidden)) { 458 debugging('showtotalsifcontainhidden should be an array', DEBUG_DEVELOPER); 459 $this->showtotalsifcontainhidden = array($courseid => $this->showtotalsifcontainhidden); 460 } 461 462 if ($this->showtotalsifcontainhidden[$courseid] == GRADE_REPORT_SHOW_REAL_TOTAL_IF_CONTAINS_HIDDEN) { 463 return array('grade' => $finalgrade, 464 'grademin' => $grademin, 465 'grademax' => $grademax, 466 'aggregationstatus' => $aggregationstatus, 467 'aggregationweight' => $aggregationweight); 468 } 469 470 // If we've moved on to another course or user, reload the grades. 471 if ($previous_userid != $this->user->id || $previous_courseid != $courseid) { 472 $hiding_affected = null; 473 $previous_userid = $this->user->id; 474 $previous_courseid = $courseid; 475 } 476 477 if (!$hiding_affected) { 478 $items = grade_item::fetch_all(array('courseid'=>$courseid)); 479 $grades = array(); 480 $sql = "SELECT g.* 481 FROM {grade_grades} g 482 JOIN {grade_items} gi ON gi.id = g.itemid 483 WHERE g.userid = {$this->user->id} AND gi.courseid = {$courseid}"; 484 if ($gradesrecords = $DB->get_records_sql($sql)) { 485 foreach ($gradesrecords as $grade) { 486 $grades[$grade->itemid] = new grade_grade($grade, false); 487 } 488 unset($gradesrecords); 489 } 490 foreach ($items as $itemid => $unused) { 491 if (!isset($grades[$itemid])) { 492 $grade_grade = new grade_grade(); 493 $grade_grade->userid = $this->user->id; 494 $grade_grade->itemid = $items[$itemid]->id; 495 $grades[$itemid] = $grade_grade; 496 } 497 $grades[$itemid]->grade_item =& $items[$itemid]; 498 } 499 $hiding_affected = grade_grade::get_hiding_affected($grades, $items); 500 } 501 502 //if the item definitely depends on a hidden item 503 if (array_key_exists($course_item->id, $hiding_affected['altered']) || 504 array_key_exists($course_item->id, $hiding_affected['alteredgrademin']) || 505 array_key_exists($course_item->id, $hiding_affected['alteredgrademax']) || 506 array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus']) || 507 array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) { 508 if (!$this->showtotalsifcontainhidden[$courseid] && array_key_exists($course_item->id, $hiding_affected['altered'])) { 509 // Hide the grade, but only when it has changed. 510 $finalgrade = null; 511 } else { 512 //use reprocessed marks that exclude hidden items 513 if (array_key_exists($course_item->id, $hiding_affected['altered'])) { 514 $finalgrade = $hiding_affected['altered'][$course_item->id]; 515 } 516 if (array_key_exists($course_item->id, $hiding_affected['alteredgrademin'])) { 517 $grademin = $hiding_affected['alteredgrademin'][$course_item->id]; 518 } 519 if (array_key_exists($course_item->id, $hiding_affected['alteredgrademax'])) { 520 $grademax = $hiding_affected['alteredgrademax'][$course_item->id]; 521 } 522 if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus'])) { 523 $aggregationstatus = $hiding_affected['alteredaggregationstatus'][$course_item->id]; 524 } 525 if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) { 526 $aggregationweight = $hiding_affected['alteredaggregationweight'][$course_item->id]; 527 } 528 529 if (!$this->showtotalsifcontainhidden[$courseid]) { 530 // If the course total is hidden we must hide the weight otherwise 531 // it can be used to compute the course total. 532 $aggregationstatus = 'unknown'; 533 $aggregationweight = null; 534 } 535 } 536 } else if (array_key_exists($course_item->id, $hiding_affected['unknowngrades'])) { 537 //not sure whether or not this item depends on a hidden item 538 if (!$this->showtotalsifcontainhidden[$courseid]) { 539 //hide the grade 540 $finalgrade = null; 541 } else { 542 //use reprocessed marks that exclude hidden items 543 $finalgrade = $hiding_affected['unknowngrades'][$course_item->id]; 544 545 if (array_key_exists($course_item->id, $hiding_affected['alteredgrademin'])) { 546 $grademin = $hiding_affected['alteredgrademin'][$course_item->id]; 547 } 548 if (array_key_exists($course_item->id, $hiding_affected['alteredgrademax'])) { 549 $grademax = $hiding_affected['alteredgrademax'][$course_item->id]; 550 } 551 if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationstatus'])) { 552 $aggregationstatus = $hiding_affected['alteredaggregationstatus'][$course_item->id]; 553 } 554 if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) { 555 $aggregationweight = $hiding_affected['alteredaggregationweight'][$course_item->id]; 556 } 557 } 558 } 559 560 return array('grade' => $finalgrade, 'grademin' => $grademin, 'grademax' => $grademax, 'aggregationstatus'=>$aggregationstatus, 'aggregationweight'=>$aggregationweight); 561 } 562 563 /** 564 * Optionally blank out course/category totals if they contain any hidden items 565 * @deprecated since Moodle 2.8 - Call blank_hidden_total_and_adjust_bounds instead. 566 * @param string $courseid the course id 567 * @param string $course_item an instance of grade_item 568 * @param string $finalgrade the grade for the course_item 569 * @return string The new final grade 570 */ 571 protected function blank_hidden_total($courseid, $course_item, $finalgrade) { 572 // Note it is flawed to call this function directly because 573 // the aggregated grade does not make sense without the updated min and max information. 574 575 debugging('grade_report::blank_hidden_total() is deprecated. 576 Call grade_report::blank_hidden_total_and_adjust_bounds instead.', DEBUG_DEVELOPER); 577 $result = $this->blank_hidden_total_and_adjust_bounds($courseid, $course_item, $finalgrade); 578 return $result['grade']; 579 } 580 581 /** 582 * Calculate average grade for a given grade item. 583 * Based on calculate_averages function from grade/report/user/lib.php 584 * 585 * @param grade_item $gradeitem Grade item 586 * @param array $info Ungraded grade items counts and report preferences. 587 * @return array Average grade and meancount. 588 */ 589 public static function calculate_average(grade_item $gradeitem, array $info): array { 590 591 $meanselection = $info['report']['meanselection']; 592 $totalcount = $info['report']['totalcount']; 593 $ungradedcounts = $info['ungradedcounts']; 594 $sumarray = $info['sumarray']; 595 596 if (empty($sumarray[$gradeitem->id])) { 597 $sumarray[$gradeitem->id] = 0; 598 } 599 600 if (empty($ungradedcounts[$gradeitem->id])) { 601 $ungradedcounts = 0; 602 } else { 603 $ungradedcounts = $ungradedcounts[$gradeitem->id]->count; 604 } 605 606 // If they want the averages to include all grade items. 607 if ($meanselection == GRADE_REPORT_MEAN_GRADED) { 608 $meancount = $totalcount - $ungradedcounts; 609 } else { 610 // Bump up the sum by the number of ungraded items * grademin. 611 $sumarray[$gradeitem->id] += ($ungradedcounts * $gradeitem->grademin); 612 $meancount = $totalcount; 613 } 614 615 $aggr['meancount'] = $meancount; 616 617 if (empty($sumarray[$gradeitem->id]) || $meancount == 0) { 618 $aggr['average'] = null; 619 } else { 620 $sum = $sumarray[$gradeitem->id]; 621 $aggr['average'] = $sum / $meancount; 622 } 623 return $aggr; 624 } 625 626 /** 627 * Get ungraded grade items info and sum of all grade items in a course. 628 * Based on calculate_averages function from grade/report/user/lib.php 629 * 630 * @return array Ungraded grade items counts with report preferences. 631 */ 632 public function ungraded_counts(): array { 633 global $DB; 634 635 $groupid = null; 636 if (isset($this->gpr->groupid)) { 637 $groupid = $this->gpr->groupid; 638 } 639 640 $info = []; 641 $info['report'] = [ 642 'averagesdisplaytype' => $this->get_pref('averagesdisplaytype'), 643 'averagesdecimalpoints' => $this->get_pref('averagesdecimalpoints'), 644 'meanselection' => $this->get_pref('meanselection'), 645 'shownumberofgrades' => $this->get_pref('shownumberofgrades'), 646 'totalcount' => $this->get_numusers(!is_null($groupid)), 647 ]; 648 649 // We want to query both the current context and parent contexts. 650 list($relatedctxsql, $relatedctxparams) = 651 $DB->get_in_or_equal($this->context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx'); 652 653 // Limit to users with a gradeable role ie students. 654 list($gradebookrolessql, $gradebookrolesparams) = 655 $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0'); 656 657 // Limit to users with an active enrolment. 658 $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); 659 $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); 660 $showonlyactiveenrol = $showonlyactiveenrol || 661 !has_capability('moodle/course:viewsuspendedusers', $this->context); 662 list($enrolledsql, $enrolledparams) = get_enrolled_sql($this->context, '', 0, $showonlyactiveenrol); 663 664 $params = array_merge($this->groupwheresql_params, $gradebookrolesparams, $enrolledparams, $relatedctxparams); 665 $params['courseid'] = $this->courseid; 666 667 // Aggregate on whole course only. 668 if (empty($groupid)) { 669 $this->groupsql = null; 670 $this->groupwheresql = null; 671 } 672 673 // Empty grades must be evaluated as grademin, NOT always 0. 674 // This query returns a count of ungraded grades (NULL finalgrade OR no matching record in grade_grades table). 675 // No join condition when joining grade_items and user to get a grade item row for every user. 676 // Then left join with grade_grades and look for rows with null final grade 677 // (which includes grade items with no grade_grade). 678 $sql = "SELECT gi.id, COUNT(u.id) AS count 679 FROM {grade_items} gi 680 JOIN {user} u ON u.deleted = 0 681 JOIN ($enrolledsql) je ON je.id = u.id 682 JOIN ( 683 SELECT DISTINCT ra.userid 684 FROM {role_assignments} ra 685 WHERE ra.roleid $gradebookrolessql 686 AND ra.contextid $relatedctxsql 687 ) rainner ON rainner.userid = u.id 688 LEFT JOIN {grade_grades} gg 689 ON (gg.itemid = gi.id AND gg.userid = u.id AND gg.finalgrade IS NOT NULL AND gg.hidden = 0) 690 $this->groupsql 691 WHERE gi.courseid = :courseid 692 AND gg.finalgrade IS NULL 693 $this->groupwheresql 694 GROUP BY gi.id"; 695 $info['ungradedcounts'] = $DB->get_records_sql($sql, $params); 696 697 // Find sums of all grade items in course. 698 $sql = "SELECT gg.itemid, SUM(gg.finalgrade) AS sum 699 FROM {grade_items} gi 700 JOIN {grade_grades} gg ON gg.itemid = gi.id 701 JOIN {user} u ON u.id = gg.userid 702 JOIN ($enrolledsql) je ON je.id = gg.userid 703 JOIN ( 704 SELECT DISTINCT ra.userid 705 FROM {role_assignments} ra 706 WHERE ra.roleid $gradebookrolessql 707 AND ra.contextid $relatedctxsql 708 ) rainner ON rainner.userid = u.id 709 $this->groupsql 710 WHERE gi.courseid = :courseid 711 AND u.deleted = 0 712 AND gg.finalgrade IS NOT NULL 713 AND gg.hidden = 0 714 $this->groupwheresql 715 GROUP BY gg.itemid"; 716 717 $sumarray = []; 718 $sums = $DB->get_recordset_sql($sql, $params); 719 foreach ($sums as $itemid => $csum) { 720 $sumarray[$itemid] = grade_floatval($csum->sum); 721 } 722 $sums->close(); 723 $info['sumarray'] = $sumarray; 724 725 return $info; 726 } 727 728 /** 729 * Get grade item type names in a course to use in filter dropdown. 730 * 731 * @return array Item types. 732 */ 733 public function item_types(): array { 734 global $DB, $CFG; 735 736 $modnames = []; 737 $sql = "(SELECT gi.itemmodule 738 FROM {grade_items} gi 739 WHERE gi.courseid = :courseid1 740 AND gi.itemmodule IS NOT NULL) 741 UNION 742 (SELECT gi1.itemtype 743 FROM {grade_items} gi1 744 WHERE gi1.courseid = :courseid2 745 AND gi1.itemtype = 'manual')"; 746 747 $itemtypes = $DB->get_records_sql($sql, ['courseid1' => $this->courseid, 'courseid2' => $this->courseid]); 748 foreach ($itemtypes as $itemtype => $value) { 749 if (file_exists("$CFG->dirroot/mod/$itemtype/lib.php")) { 750 $modnames[$itemtype] = get_string("modulename", $itemtype, null, true); 751 } else if ($itemtype == 'manual') { 752 $modnames[$itemtype] = get_string('manualitem', 'grades', null, true); 753 } 754 } 755 756 return $modnames; 757 } 758 759 /** 760 * Load a valid list of gradable users in a course. 761 * 762 * @param int $courseid The course ID. 763 * @param int|null $groupid The group ID (optional). 764 * @return array A list of enrolled gradable users. 765 */ 766 public static function get_gradable_users(int $courseid, ?int $groupid = null): array { 767 global $CFG; 768 require_once($CFG->dirroot . '/grade/lib.php'); 769 770 $context = context_course::instance($courseid); 771 $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); 772 $onlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol) || 773 !has_capability('moodle/course:viewsuspendedusers', $context); 774 775 return get_gradable_users($courseid, $groupid, $onlyactiveenrol); 776 } 777 778 } 779
title
Description
Body
title
Description
Body
title
Description
Body
title
Body