webservice MDL-21524 add role_assign and role_unassign ws functions
[moodle.git] / grade / report / grader / lib.php
CommitLineData
e060e33d 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
8ad36f4c 17
4ba9941c 18/**
19 * File in which the grader_report class is defined.
20 * @package gradebook
21 */
22
eea6690a 23require_once($CFG->dirroot . '/grade/report/lib.php');
4ba9941c 24require_once($CFG->libdir.'/tablelib.php');
4ba9941c 25
26/**
27 * Class providing an API for the grader report building and displaying.
eea6690a 28 * @uses grade_report
4ba9941c 29 * @package gradebook
30 */
eea6690a 31class grade_report_grader extends grade_report {
4ba9941c 32 /**
33 * The final grades.
b89a70ce 34 * @var array $grades
4ba9941c 35 */
d24832f9 36 public $grades;
4ba9941c 37
38 /**
39 * Array of errors for bulk grades updating.
40 * @var array $gradeserror
41 */
d24832f9 42 public $gradeserror = array();
4ba9941c 43
4ba9941c 44//// SQL-RELATED
45
4ba9941c 46 /**
47 * The id of the grade_item by which this report will be sorted.
48 * @var int $sortitemid
49 */
d24832f9 50 public $sortitemid;
4ba9941c 51
52 /**
53 * Sortorder used in the SQL selections.
54 * @var int $sortorder
55 */
d24832f9 56 public $sortorder;
4ba9941c 57
58 /**
59 * An SQL fragment affecting the search for users.
60 * @var string $userselect
61 */
d24832f9 62 public $userselect;
63
64 /**
65 * The bound params for $userselect
319770d7 66 * @var array $userselectparams
d24832f9 67 */
319770d7 68 public $userselectparams = array();
4ba9941c 69
4faf5f99 70 /**
384960dd 71 * List of collapsed categories from user preference
4faf5f99 72 * @var array $collapsed
73 */
d24832f9 74 public $collapsed;
4faf5f99 75
66ef0471 76 /**
77 * A count of the rows, used for css classes.
78 * @var int $rowcount
79 */
d24832f9 80 public $rowcount = 0;
66ef0471 81
6cc3e350 82 /**
57068674 83 * Capability check caching
84 * */
d24832f9 85 public $canviewhidden;
57068674 86
319770d7 87 var $preferencespage=false;
653a8648 88
4ba9941c 89 /**
90 * Constructor. Sets local copies of user preferences and initialises grade_tree.
91 * @param int $courseid
d30c4481 92 * @param object $gpr grade plugin return tracking object
eea6690a 93 * @param string $context
94 * @param int $page The current page being viewed (when report is paged)
95 * @param int $sortitemid The id of the grade_item by which to sort the table
4ba9941c 96 */
d24832f9 97 public function __construct($courseid, $gpr, $context, $page=null, $sortitemid=null) {
4ba9941c 98 global $CFG;
d24832f9 99 parent::__construct($courseid, $gpr, $context, $page);
4ba9941c 100
57068674 101 $this->canviewhidden = has_capability('moodle/grade:viewhidden', get_context_instance(CONTEXT_COURSE, $this->course->id));
102
4faf5f99 103 // load collapsed settings for this report
104 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) {
105 $this->collapsed = unserialize($collapsed);
4faf5f99 106 } else {
384960dd 107 $this->collapsed = array('aggregatesonly' => array(), 'gradesonly' => array());
4faf5f99 108 }
384960dd 109
aea4df41 110 if (empty($CFG->enableoutcomes)) {
111 $nooutcomes = false;
112 } else {
113 $nooutcomes = get_user_preferences('grade_report_shownooutcomes');
114 }
115
e0724506 116 // if user report preference set or site report setting set use it, otherwise use course or site setting
117 $switch = $this->get_pref('aggregationposition');
05766b50 118 if ($switch == '') {
e0724506 119 $switch = grade_get_setting($this->courseid, 'aggregationposition', $CFG->grade_aggregationposition);
120 }
121
4faf5f99 122 // Grab the grade_tree for this course
e0724506 123 $this->gtree = new grade_tree($this->courseid, true, $switch, $this->collapsed, $nooutcomes);
4faf5f99 124
4ba9941c 125 $this->sortitemid = $sortitemid;
126
4ba9941c 127 // base url for sorting by first/last name
09cef06a 128
319770d7 129 $this->baseurl = new moodle_url('index.php', array('id' => $this->courseid));
130
131 $studentsperpage = $this->get_pref('studentsperpage');
09cef06a 132 if (!empty($studentsperpage)) {
319770d7 133 $this->baseurl->params(array('perpage' => $studentsperpage, 'page' => $this->page));
09cef06a 134 }
09cef06a 135
a6855934 136 $this->pbarurl = new moodle_url('/grade/report/grader/index.php', array('id' => $this->courseid, 'perpage' => $studentsperpage));
4ba9941c 137
35079f53 138 $this->setup_groups();
4ba9941c 139
140 $this->setup_sortitemid();
141 }
142
4ba9941c 143 /**
144 * Processes the data sent by the form (grades and feedbacks).
75674470 145 * Caller is reposible for all access control checks
146 * @param array $data form submission (with magic quotes)
147 * @return array empty array if success, array of warnings if something fails.
4ba9941c 148 */
d24832f9 149 public function process_data($data) {
5c75a0a3 150 global $DB;
75674470 151 $warnings = array();
2cc773f5 152
30ebb74f 153 $separategroups = false;
154 $mygroups = array();
155 if ($this->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $this->context)) {
156 $separategroups = true;
157 $mygroups = groups_get_user_groups($this->course->id);
158 $mygroups = $mygroups[0]; // ignore groupings
159 // reorder the groups fro better perf bellow
160 $current = array_search($this->currentgroup, $mygroups);
161 if ($current !== false) {
162 unset($mygroups[$current]);
163 array_unshift($mygroups, $this->currentgroup);
164 }
165 }
166
4ba9941c 167 // always initialize all arrays
168 $queue = array();
4ba9941c 169 foreach ($data as $varname => $postedvalue) {
4ba9941c 170
171 $needsupdate = false;
4ba9941c 172
173 // skip, not a grade nor feedback
79eabc2a 174 if (strpos($varname, 'grade') === 0) {
319770d7 175 $datatype = 'grade';
79eabc2a 176 } else if (strpos($varname, 'feedback') === 0) {
319770d7 177 $datatype = 'feedback';
4ba9941c 178 } else {
179 continue;
180 }
181
182 $gradeinfo = explode("_", $varname);
4ba9941c 183 $userid = clean_param($gradeinfo[1], PARAM_INT);
184 $itemid = clean_param($gradeinfo[2], PARAM_INT);
185
29a5680e 186 $oldvalue = $data->{'old'.$varname};
187
188 // was change requested?
99ccfda8 189 if ($oldvalue == $postedvalue) { // string comparison
29a5680e 190 continue;
191 }
192
319770d7 193 if (!$gradeitem = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here!
14398fd6 194 print_error('invalidgradeitmeid');
4ba9941c 195 }
196
197 // Pre-process grade
319770d7 198 if ($datatype == 'grade') {
4256a134 199 $feedback = false;
200 $feedbackformat = false;
319770d7 201 if ($gradeitem->gradetype == GRADE_TYPE_SCALE) {
4ba9941c 202 if ($postedvalue == -1) { // -1 means no grade
203 $finalgrade = null;
204 } else {
29a5680e 205 $finalgrade = $postedvalue;
4ba9941c 206 }
207 } else {
76317c73 208 $finalgrade = unformat_float($postedvalue);
4ba9941c 209 }
79eabc2a 210
0a2c8485 211 $errorstr = '';
379ea949 212 // Warn if the grade is out of bounds.
213 if (is_null($finalgrade)) {
214 // ok
653a8648 215 } else {
319770d7 216 $bounded = $gradeitem->bounded_grade($finalgrade);
653a8648 217 if ($bounded > $finalgrade) {
0a2c8485 218 $errorstr = 'lessthanmin';
653a8648 219 } else if ($bounded < $finalgrade) {
78946b9b
PS
220 $errorstr = 'morethanmax';
221 }
0a2c8485 222 }
223 if ($errorstr) {
5c75a0a3 224 $user = $DB->get_record('user', array('id' => $userid), 'id, firstname, lastname');
379ea949 225 $gradestr = new object();
0a2c8485 226 $gradestr->username = fullname($user);
319770d7 227 $gradestr->itemname = $gradeitem->get_name();
9d35e66e 228 $warnings[] = get_string($errorstr, 'grades', $gradestr);
0a2c8485 229 }
230
319770d7 231 } else if ($datatype == 'feedback') {
4256a134 232 $finalgrade = false;
79eabc2a 233 $trimmed = trim($postedvalue);
234 if (empty($trimmed)) {
29a5680e 235 $feedback = NULL;
e1d2692a 236 } else {
653a8648 237 $feedback = stripslashes($postedvalue);
e1d2692a 238 }
4ba9941c 239 }
4ba9941c 240
30ebb74f 241 // group access control
242 if ($separategroups) {
243 // note: we can not use $this->currentgroup because it would fail badly
244 // when having two browser windows each with different group
245 $sharinggroup = false;
246 foreach($mygroups as $groupid) {
247 if (groups_is_member($groupid, $userid)) {
248 $sharinggroup = true;
249 break;
250 }
251 }
252 if (!$sharinggroup) {
253 // either group membership changed or somebedy is hacking grades of other group
254 $warnings[] = get_string('errorsavegrade', 'grades');
255 continue;
256 }
257 }
258
319770d7 259 $gradeitem->update_final_grade($userid, $finalgrade, 'gradebook', $feedback, FORMAT_MOODLE);
4ba9941c 260 }
261
75674470 262 return $warnings;
4ba9941c 263 }
264
4ba9941c 265
266 /**
267 * Setting the sort order, this depends on last state
268 * all this should be in the new table class that we might need to use
269 * for displaying grades.
270 */
d24832f9 271 private function setup_sortitemid() {
63d6efa2 272
273 global $SESSION;
274
4ba9941c 275 if ($this->sortitemid) {
276 if (!isset($SESSION->gradeuserreport->sort)) {
a80112f0 277 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
278 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
279 } else {
280 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
281 }
4ba9941c 282 } else {
283 // this is the first sort, i.e. by last name
284 if (!isset($SESSION->gradeuserreport->sortitemid)) {
a80112f0 285 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
d24832f9 286 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
a80112f0 287 } else {
288 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
289 }
4ba9941c 290 } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) {
291 // same as last sort
292 if ($SESSION->gradeuserreport->sort == 'ASC') {
293 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
294 } else {
295 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
296 }
297 } else {
a80112f0 298 if ($this->sortitemid == 'firstname' || $this->sortitemid == 'lastname') {
299 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
300 } else {
301 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
302 }
4ba9941c 303 }
304 }
305 $SESSION->gradeuserreport->sortitemid = $this->sortitemid;
306 } else {
307 // not requesting sort, use last setting (for paging)
308
309 if (isset($SESSION->gradeuserreport->sortitemid)) {
310 $this->sortitemid = $SESSION->gradeuserreport->sortitemid;
d20737e8 311 }else{
312 $this->sortitemid = 'lastname';
4ba9941c 313 }
d20737e8 314
4ba9941c 315 if (isset($SESSION->gradeuserreport->sort)) {
316 $this->sortorder = $SESSION->gradeuserreport->sort;
317 } else {
318 $this->sortorder = 'ASC';
319 }
320 }
321 }
322
4ba9941c 323 /**
b50371da 324 * pulls out the userids of the users to be display, and sorts them
4ba9941c 325 */
d24832f9 326 public function load_users() {
327 global $CFG, $DB;
b50371da 328
319770d7 329 list($usql, $gbrparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0');
4ba9941c 330
331 if (is_numeric($this->sortitemid)) {
319770d7 332 $params = array_merge(array('gitemid'=>$this->sortitemid), $gbrparams, $this->groupwheresql_params);
b50371da 333 // the MAX() magic is required in order to please PG
334 $sort = "MAX(g.finalgrade) $this->sortorder";
09d0ef21 335
9d35e66e 336 $sql = "SELECT u.id, u.firstname, u.lastname, u.imagealt, u.picture, u.idnumber
b50371da 337 FROM {user} u
338 JOIN {role_assignments} ra ON ra.userid = u.id
09d0ef21 339 $this->groupsql
b50371da 340 LEFT JOIN {grade_grades} g ON (g.userid = u.id AND g.itemid = :gitemid)
341 WHERE ra.roleid $usql AND u.deleted = 0
09d0ef21 342 $this->groupwheresql
343 AND ra.contextid ".get_related_contexts_string($this->context)."
b50371da 344 GROUP BY u.id, u.firstname, u.lastname, u.imagealt, u.picture, u.idnumber
09d0ef21 345 ORDER BY $sort";
346
4ba9941c 347 } else {
09d0ef21 348 switch($this->sortitemid) {
349 case 'lastname':
350 $sort = "u.lastname $this->sortorder, u.firstname $this->sortorder"; break;
351 case 'firstname':
352 $sort = "u.firstname $this->sortorder, u.lastname $this->sortorder"; break;
353 case 'idnumber':
354 default:
355 $sort = "u.idnumber $this->sortorder"; break;
c421ad4b 356 }
4ba9941c 357
319770d7 358 $params = array_merge($gbrparams, $this->groupwheresql_params);
b50371da 359 $sql = "SELECT DISTINCT u.id, u.firstname, u.lastname, u.imagealt, u.picture, u.idnumber
d24832f9 360 FROM {user} u
361 JOIN {role_assignments} ra ON u.id = ra.userid
09d0ef21 362 $this->groupsql
b50371da 363 WHERE ra.roleid $usql AND u.deleted = 0
09d0ef21 364 $this->groupwheresql
365 AND ra.contextid ".get_related_contexts_string($this->context)."
366 ORDER BY $sort";
4ba9941c 367 }
368
09d0ef21 369
d24832f9 370 $this->users = $DB->get_records_sql($sql, $params, $this->get_pref('studentsperpage') * $this->page, $this->get_pref('studentsperpage'));
09d0ef21 371
4ba9941c 372 if (empty($this->users)) {
373 $this->userselect = '';
374 $this->users = array();
0a695c36 375 $this->userselect_params = array();
4ba9941c 376 } else {
b50371da 377 list($usql, $params) = $DB->get_in_or_equal(array_keys($this->users), SQL_PARAMS_NAMED, 'usid0');
d24832f9 378 $this->userselect = "AND g.userid $usql";
379 $this->userselect_params = $params;
4ba9941c 380 }
381
382 return $this->users;
383 }
384
4ba9941c 385 /**
386 * we supply the userids in this query, and get all the grades
387 * pulls out all the grades, this does not need to worry about paging
388 */
d24832f9 389 public function load_final_grades() {
390 global $CFG, $DB;
4ba9941c 391
23207a1a 392 // please note that we must fetch all grade_grades fields if we want to contruct grade_grade object from it!
b50371da 393 $params = array_merge(array('courseid'=>$this->courseid), $this->userselect_params);
b89a70ce 394 $sql = "SELECT g.*
d24832f9 395 FROM {grade_items} gi,
396 {grade_grades} g
b50371da 397 WHERE g.itemid = gi.id AND gi.courseid = :courseid {$this->userselect}";
b89a70ce 398
399 $userids = array_keys($this->users);
4ba9941c 400
d297269d 401
d24832f9 402 if ($grades = $DB->get_records_sql($sql, $params)) {
b89a70ce 403 foreach ($grades as $graderec) {
d24832f9 404 if (in_array($graderec->userid, $userids) and array_key_exists($graderec->itemid, $this->gtree->get_items())) { // some items may not be present!!
b89a70ce 405 $this->grades[$graderec->userid][$graderec->itemid] = new grade_grade($graderec, false);
d24832f9 406 $this->grades[$graderec->userid][$graderec->itemid]->grade_item =& $this->gtree->get_item($graderec->itemid); // db caching
b89a70ce 407 }
408 }
409 }
410
411 // prefil grades that do not exist yet
412 foreach ($userids as $userid) {
d24832f9 413 foreach ($this->gtree->get_items() as $itemid=>$unused) {
b89a70ce 414 if (!isset($this->grades[$userid][$itemid])) {
415 $this->grades[$userid][$itemid] = new grade_grade();
478f4322 416 $this->grades[$userid][$itemid]->itemid = $itemid;
3b34f698 417 $this->grades[$userid][$itemid]->userid = $userid;
d24832f9 418 $this->grades[$userid][$itemid]->grade_item =& $this->gtree->get_item($itemid); // db caching
b89a70ce 419 }
4ba9941c 420 }
421 }
422 }
423
424 /**
425 * Builds and returns a div with on/off toggles.
426 * @return string HTML code
427 */
d24832f9 428 public function get_toggles_html() {
319770d7 429 global $CFG, $USER, $COURSE, $OUTPUT;
aea4df41 430
319770d7 431 $html = '';
2cc773f5 432 if ($USER->gradeediting[$this->courseid]) {
433 if (has_capability('moodle/grade:manage', $this->context) or has_capability('moodle/grade:hide', $this->context)) {
319770d7 434 $html .= $this->print_toggle('eyecons');
2cc773f5 435 }
436 if (has_capability('moodle/grade:manage', $this->context)
437 or has_capability('moodle/grade:lock', $this->context)
438 or has_capability('moodle/grade:unlock', $this->context)) {
319770d7 439 $html .= $this->print_toggle('locks');
2cc773f5 440 }
2ca093fa 441 if (has_capability('moodle/grade:manage', $this->context)) {
319770d7 442 $html .= $this->print_toggle('quickfeedback');
2ca093fa 443 }
444
2cc773f5 445 if (has_capability('moodle/grade:manage', $this->context)) {
319770d7 446 $html .= $this->print_toggle('calculations');
2cc773f5 447 }
4ba9941c 448 }
449
57068674 450 if ($this->canviewhidden) {
319770d7 451 $html .= $this->print_toggle('averages');
57068674 452 }
aae94377 453
319770d7 454 $html .= $this->print_toggle('ranges');
aea4df41 455 if (!empty($CFG->enableoutcomes)) {
319770d7 456 $html .= $this->print_toggle('nooutcomes');
aea4df41 457 }
319770d7 458
459 return $OUTPUT->container($html, 'grade-report-toggles');
4ba9941c 460 }
461
462 /**
463 * Shortcut function for printing the grader report toggles.
464 * @param string $type The type of toggle
465 * @param bool $return Whether to return the HTML string rather than printing it
466 * @return void
467 */
319770d7 468 public function print_toggle($type) {
e63f88c9 469 global $CFG, $OUTPUT;
4ba9941c 470
319770d7 471 $icons = array('eyecons' => 't/hide',
472 'calculations' => 't/calc',
473 'locks' => 't/lock',
474 'averages' => 't/mean',
475 'quickfeedback' => 't/feedback',
476 'nooutcomes' => 't/outcomes');
4ba9941c 477
319770d7 478 $prefname = 'grade_report_show' . $type;
aea4df41 479
319770d7 480 if (array_key_exists($prefname, $CFG)) {
481 $showpref = get_user_preferences($prefname, $CFG->$prefname);
aea4df41 482 } else {
319770d7 483 $showpref = get_user_preferences($prefname);
aea4df41 484 }
4ba9941c 485
388234f4 486 $strshow = $this->get_lang_string('show' . $type, 'grades');
487 $strhide = $this->get_lang_string('hide' . $type, 'grades');
4ba9941c 488
319770d7 489 $showhide = 'show';
490 $toggleaction = 1;
4ba9941c 491
319770d7 492 if ($showpref) {
493 $showhide = 'hide';
494 $toggleaction = 0;
4ba9941c 495 }
496
497 if (array_key_exists($type, $icons)) {
319770d7 498 $imagename = $icons[$type];
4ba9941c 499 } else {
319770d7 500 $imagename = "t/$type.gif";
4ba9941c 501 }
502
319770d7 503 $string = ${'str' . $showhide};
4ba9941c 504
8ae8bf8a
PS
505 $aurl = clone($this->baseurl);
506 $url->params(array('toggle' => $toggleaction, 'toggle_type' => $type));
4ba9941c 507
8ae8bf8a 508 $retval = $OUTPUT->container($OUTPUT->action_icon($url, $string, $imagename, array('class'=>'iconsmall'))); // TODO: this container looks wrong here
4ba9941c 509
319770d7 510 return $retval;
4ba9941c 511 }
512
513 /**
6c096a49
NC
514 * Builds and returns the rows that will make up the left part of the grader report
515 * This consists of student names and icons, links to user reports and id numbers, as well
516 * as header cells for these columns. It also includes the fillers required for the
517 * categories displayed on the right side of the report.
518 * @return array Array of html_table_row objects
4ba9941c 519 */
6c096a49 520 public function get_left_rows() {
319770d7 521 global $CFG, $USER, $OUTPUT;
4ba9941c 522
6c096a49
NC
523 $rows = array();
524
525 $showuserimage = $this->get_pref('showuserimage');
526 $showuseridnumber = $this->get_pref('showuseridnumber');
795b745a 527 $fixedstudents = $this->is_fixed_students();
203b7e2e 528
6c096a49
NC
529 $strfeedback = $this->get_lang_string("feedback");
530 $strgrade = $this->get_lang_string('grade');
203b7e2e 531
6c096a49
NC
532 $arrows = $this->get_sort_arrows();
533
534 $colspan = 1;
535
3e7ca6b3 536 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) {
6c096a49
NC
537 $colspan++;
538 }
539
540 if ($showuseridnumber) {
541 $colspan++;
542 }
543
544 $levels = count($this->gtree->levels) - 1;
545
546 for ($i = 0; $i < $levels; $i++) {
547 $fillercell = new html_table_cell();
548 $fillercell->add_classes(array('fixedcolumn', 'cell', 'c0', 'topleft'));
549 $fillercell->text = ' ';
550 $fillercell->colspan = $colspan;
551 $row = html_table_row::make(array($fillercell));
552 $rows[] = $row;
553 }
554
555 $headerrow = new html_table_row();
556 $headerrow->add_class('heading');
557
558 $studentheader = new html_table_cell();
559 $studentheader->add_classes(array('header', 'c0'));
560 $studentheader->scope = 'col';
561 $studentheader->header = true;
562 $studentheader->id = 'studentheader';
3e7ca6b3 563 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) {
6c096a49
NC
564 $studentheader->colspan = 2;
565 }
566 $studentheader->text = $arrows['studentname'];
567
568 $headerrow->cells[] = $studentheader;
569
570 if ($showuseridnumber) {
0f4c64b7
PS
571 // TODO: weird, this is not used anywhere
572 $sortidnumberlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'idnumber')), get_string('idnumber'));
6c096a49
NC
573
574 $idnumberheader = new html_table_cell();
575 $idnumberheader->add_classes(array('header', 'c0', 'useridnumber'));
576 $idnumberheader->scope = 'col';
577 $idnumberheader->header = true;
578 $idnumberheader->text = $arrows['idnumber'];
579
580 $headerrow->cells[] = $idnumberheader;
581 }
582
583 $rows[] = $headerrow;
584
585 $rows = $this->get_left_icons_row($rows, $colspan);
586
587 $rowclasses = array('even', 'odd');
588
589 foreach ($this->users as $userid => $user) {
590 $userrow = new html_table_row();
591 $userrow->add_classes(array('r'.$this->rowcount++, $rowclasses[$this->rowcount % 2]));
592
593 $usercell = new html_table_cell();
594 $usercell->add_classes(array('c0', 'user'));
595 $usercell->header = true;
596 $usercell->scope = 'row';
597 $usercell->add_action('click', 'yui_set_row');
598
599 if ($showuserimage) {
812dbaf7 600 $usercell->text = $OUTPUT->container($OUTPUT->user_picture($user), 'userpic');
203b7e2e 601 }
602
a6855934 603 $usercell->text .= html_writer::link(new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $this->course->id)), fullname($user));
6c096a49
NC
604
605 $userrow->cells[] = $usercell;
606
3e7ca6b3 607 if (has_capability('gradereport/'.$CFG->grade_profilereport.':view', $this->context)) {
6c096a49
NC
608 $userreportcell = new html_table_cell();
609 $userreportcell->add_class('userreport');
610 $userreportcell->header = true;
611 $a->user = fullname($user);
612 $strgradesforuser = get_string('gradesforuser', 'grades', $a);
a6855934 613 $url = new moodle_url('/grade/report/'.$CFG->grade_profilereport.'/index.php', array('userid' => $user->id, 'id' => $this->course->id));
8ae8bf8a 614 $userreportcell->text = $OUTPUT->action_icon($url, $strgradesforuser, 't/grades', array('class'=>'iconsmall'));
6c096a49 615 $userrow->cells[] = $userreportcell;
203b7e2e 616 }
617
6c096a49
NC
618 if ($showuseridnumber) {
619 $idnumbercell = new html_table_cell();
620 $idnumbercell->add_classes(array('header', 'c0', 'useridnumber'));
621 $idnumbercell->header = true;
622 $idnumbercell->scope = 'row';
623 $idnumbercell->add_action('click', 'yui_set_row');
624 $userrow->cells[] = $idnumbercell;
625 }
626
627 $rows[] = $userrow;
203b7e2e 628 }
4ba9941c 629
6c096a49
NC
630 $rows = $this->get_left_range_row($rows, $colspan);
631 $rows = $this->get_left_avg_row($rows, $colspan, true);
632 $rows = $this->get_left_avg_row($rows, $colspan);
4ba9941c 633
6c096a49
NC
634 return $rows;
635 }
636
637 /**
638 * Builds and returns the rows that will make up the right part of the grader report
639 * @return array Array of html_table_row objects
640 */
641 public function get_right_rows() {
405a526f 642 global $CFG, $USER, $OUTPUT, $DB;
4ba9941c 643
6c096a49
NC
644 $rows = array();
645 $this->rowcount = 0;
646 $numrows = count($this->gtree->get_levels());
647 $numusers = count($this->users);
648 $gradetabindex = 1;
319770d7 649 $columnstounset = array();
6c096a49
NC
650 $strgrade = $this->get_lang_string('grade');
651 $arrows = $this->get_sort_arrows();
cb7fe7b4 652
d24832f9 653 foreach ($this->gtree->get_levels() as $key=>$row) {
66ef0471 654 $columncount = 0;
4ba9941c 655 if ($key == 0) {
cb7fe7b4 656 // do not display course grade category
4ba9941c 657 // continue;
658 }
659
6c096a49
NC
660 $headingrow = new html_table_row();
661 $headingrow->add_class('heading_name_row');
4ba9941c 662
cb7fe7b4 663 foreach ($row as $columnkey => $element) {
319770d7 664 $sortlink = clone($this->baseurl);
2e3987a9 665 if (isset($element['object']->id)) {
319770d7 666 $sortlink->param('sortitemid', $element['object']->id);
2e3987a9 667 }
668
cb7fe7b4 669 $eid = $element['eid'];
670 $object = $element['object'];
671 $type = $element['type'];
438a5aa9 672 $categorystate = @$element['categorystate'];
2e3987a9 673 $itemmodule = null;
674 $iteminstance = null;
8c5a416e 675
66ef0471 676 $columnclass = 'c' . $columncount++;
4ba9941c 677 if (!empty($element['colspan'])) {
6c096a49 678 $colspan = $element['colspan'];
66ef0471 679 $columnclass = '';
4ba9941c 680 } else {
6c096a49 681 $colspan = 1;
4ba9941c 682 }
683
684 if (!empty($element['depth'])) {
6c096a49 685 $catlevel = 'catlevel'.$element['depth'];
4ba9941c 686 } else {
687 $catlevel = '';
688 }
689
cb7fe7b4 690// Element is a filler
4ba9941c 691 if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') {
6c096a49
NC
692 $fillercell = new html_table_cell();
693 $fillercell->add_classes(array($columnclass, $type, $catlevel));
694 $fillercell->colspan = $colspan;
695 $fillercell->text = '&nbsp;';
696 $fillercell->header = true;
697 $fillercell->scope = 'col';
698 $headingrow->cells[] = $fillercell;
cb7fe7b4 699 }
700// Element is a category
4faf5f99 701 else if ($type == 'category') {
6c096a49
NC
702 $categorycell = new html_table_cell();
703 $categorycell->add_classes(array($columnclass, 'category', $catlevel));
704 $categorycell->colspan = $colspan;
705 $categorycell->text = shorten_text($element['object']->get_name());
706 $categorycell->text .= $this->get_collapsing_icon($element);
707 $categorycell->header = true;
708 $categorycell->scope = 'col';
4ba9941c 709
710 // Print icons
2cc773f5 711 if ($USER->gradeediting[$this->courseid]) {
6c096a49 712 $categorycell->text .= $this->get_icons($element);
4ba9941c 713 }
714
6c096a49 715 $headingrow->cells[] = $categorycell;
cb7fe7b4 716 }
717// Element is a grade_item
4faf5f99 718 else {
2e3987a9 719 $itemmodule = $element['object']->itemmodule;
720 $iteminstance = $element['object']->iteminstance;
721
4ba9941c 722 if ($element['object']->id == $this->sortitemid) {
723 if ($this->sortorder == 'ASC') {
319770d7 724 $arrow = $this->get_sort_arrow('up', $sortlink);
4ba9941c 725 } else {
319770d7 726 $arrow = $this->get_sort_arrow('down', $sortlink);
4ba9941c 727 }
728 } else {
319770d7 729 $arrow = $this->get_sort_arrow('move', $sortlink);
4ba9941c 730 }
731
6c096a49
NC
732 $headerlink = $this->gtree->get_element_header($element, true, $this->get_pref('showactivityicons'), false);
733
734 $itemcell = new html_table_cell();
735 $itemcell->add_classes(array($columnclass, $type, $catlevel));
736
4ba9941c 737 if ($element['object']->is_hidden()) {
6c096a49 738 $itemcell->add_class('hidden');
4ba9941c 739 }
740
6c096a49
NC
741 $itemcell->colspan = $colspan;
742 $itemcell->text = shorten_text($headerlink) . $arrow;
743 $itemcell->header = true;
744 $itemcell->scope = 'col';
745 $itemcell->onclick = 'set_col(this.cellIndex);';
746
747 $headingrow->cells[] = $itemcell;
4ba9941c 748 }
749
750 }
6c096a49 751 $rows[] = $headingrow;
4ba9941c 752 }
4ba9941c 753
6c096a49 754 $rows = $this->get_right_icons_row($rows);
4ba9941c 755
388234f4 756 // Preload scale objects for items with a scaleid
319770d7 757 $scaleslist = array();
c0c1e7c2 758 $tabindices = array();
d297269d 759
d24832f9 760 foreach ($this->gtree->get_items() as $item) {
388234f4 761 if (!empty($item->scaleid)) {
319770d7 762 $scaleslist[] = $item->scaleid;
388234f4 763 }
d297269d 764
c0c1e7c2 765 $tabindices[$item->id]['grade'] = $gradetabindex;
766 $tabindices[$item->id]['feedback'] = $gradetabindex + $numusers;
767 $gradetabindex += $numusers * 2;
388234f4 768 }
319770d7 769 $scalesarray = array();
388234f4 770
319770d7 771 if (!empty($scaleslist)) {
772 $scalesarray = $DB->get_records_list('scale', 'id', $scaleslist);
388234f4 773 }
d24832f9 774
6c096a49 775 $rowclasses = array('even', 'odd');
a47d38f2 776
4ba9941c 777 foreach ($this->users as $userid => $user) {
b89a70ce 778
57068674 779 if ($this->canviewhidden) {
d297269d 780 $altered = array();
781 $unknown = array();
b89a70ce 782 } else {
319770d7 783 $hidingaffected = grade_grade::get_hiding_affected($this->grades[$userid], $this->gtree->get_items());
784 $altered = $hidingaffected['altered'];
785 $unknown = $hidingaffected['unknown'];
786 unset($hidingaffected);
b89a70ce 787 }
788
66ef0471 789 $columncount = 0;
bb9b58a6 790
6c096a49
NC
791 $itemrow = new html_table_row();
792 $itemrow->add_class($rowclasses[$this->rowcount % 2]);
9d35e66e 793
dc482cfa 794 foreach ($this->gtree->items as $itemid=>$unused) {
795 $item =& $this->gtree->items[$itemid];
d297269d 796 $grade = $this->grades[$userid][$item->id];
b89a70ce 797
6c096a49
NC
798 $itemcell = new html_table_cell();
799
e50ce569 800 // Get the decimal points preference for this item
31a6c06c 801 $decimalpoints = $item->get_decimals();
4ba9941c 802
d297269d 803 if (in_array($itemid, $unknown)) {
804 $gradeval = null;
805 } else if (array_key_exists($itemid, $altered)) {
806 $gradeval = $altered[$itemid];
807 } else {
808 $gradeval = $grade->finalgrade;
809 }
4ba9941c 810
00374cc5 811 // MDL-11274
812 // Hide grades in the grader report if the current grader doesn't have 'moodle/grade:viewhidden'
57068674 813 if (!$this->canviewhidden and $grade->is_hidden()) {
6cc3e350 814 if (!empty($CFG->grade_hiddenasdate) and $grade->get_datesubmitted() and !$item->is_category_item() and !$item->is_course_item()) {
d297269d 815 // the problem here is that we do not have the time when grade value was modified, 'timemodified' is general modification date for grade_grades records
6c096a49 816 $itemcell->text = $OUTPUT->span(userdate($grade->get_datesubmitted(),get_string('strftimedatetimeshort')), 'datesubmitted');
bb49f77b 817 } else {
6c096a49 818 $itemcell->text = '-';
00374cc5 819 }
6c096a49 820 $itemrow->cells[] = $itemcell;
a5b8be62 821 continue;
00374cc5 822 }
823
2cc773f5 824 // emulate grade element
d3c3da1b 825 $eid = $this->gtree->get_grade_eid($grade);
826 $element = array('eid'=>$eid, 'object'=>$grade, 'type'=>'grade');
2cc773f5 827
6c096a49 828 $itemcell->add_class('grade');
dff9d94d 829 if ($item->is_category_item()) {
6c096a49 830 $itemcell->add_class('cat');
b89a70ce 831 }
dff9d94d 832 if ($item->is_course_item()) {
6c096a49 833 $itemcell->add_class('course');
b89a70ce 834 }
4ba9941c 835 if ($grade->is_overridden()) {
6c096a49 836 $itemcell->add_class('overridden');
4ba9941c 837 }
85db09fb 838
5ebce7bb 839 if ($grade->is_excluded()) {
6c096a49 840 // $itemcell->add_class('excluded');
5ebce7bb 841 }
4ba9941c 842
319770d7 843 $gradetitle = $OUTPUT->container(fullname($user), 'fullname');
844 $gradetitle .= $OUTPUT->container($item->get_name(true), 'itemname');
653a8648 845
846 if (!empty($grade->feedback) && !$USER->gradeediting[$this->courseid]) {
319770d7 847 $gradetitle .= $OUTPUT->container(wordwrap(trim(format_string($grade->feedback, $grade->feedbackformat)), 34, '<br/ >'), 'feedback');
653a8648 848 }
849
6c096a49 850 $itemcell->title = s($gradetitle);
dff9d94d 851
23207a1a 852 if ($grade->is_excluded()) {
6c096a49 853 $itemcell->text .= $OUTPUT->span(get_string('excluded', 'grades'), 'excludedfloater');
23207a1a 854 }
855
4ba9941c 856 // Do not show any icons if no grade (no record in DB to match)
d3c3da1b 857 if (!$item->needsupdate and $USER->gradeediting[$this->courseid]) {
6c096a49 858 $itemcell->text .= $this->get_icons($element);
4ba9941c 859 }
860
80fb1cf9 861 $hidden = '';
862 if ($grade->is_hidden()) {
863 $hidden = ' hidden ';
864 }
6cc3e350 865
d24832f9 866 $gradepass = ' gradefail ';
46e6df89 867 if ($grade->is_passed($item)) {
868 $gradepass = ' gradepass ';
e6477988 869 } elseif (is_null($grade->is_passed($item))) {
870 $gradepass = '';
46e6df89 871 }
872
4ba9941c 873 // if in editting mode, we need to print either a text box
874 // or a drop down (for scales)
4ba9941c 875 // grades in item of type grade category or course are not directly editable
d14ae855 876 if ($item->needsupdate) {
6c096a49 877 $itemcell->text .= $OUTPUT->span(get_string('error'), "gradingerror$hidden");
d14ae855 878
2cc773f5 879 } else if ($USER->gradeediting[$this->courseid]) {
4ba9941c 880
319770d7 881 if ($item->scaleid && !empty($scalesarray[$item->scaleid])) {
882 $scale = $scalesarray[$item->scaleid];
99ccfda8 883 $gradeval = (int)$gradeval; // scales use only integers
388234f4 884 $scales = explode(",", $scale->scale);
885 // reindex because scale is off 1
9d35e66e 886
914ea002 887 // MDL-12104 some previous scales might have taken up part of the array
888 // so this needs to be reset
d48ebf97 889 $scaleopt = array();
388234f4 890 $i = 0;
891 foreach ($scales as $scaleoption) {
892 $i++;
893 $scaleopt[$i] = $scaleoption;
894 }
895
896 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
0658afc9 897 $oldval = empty($gradeval) ? -1 : $gradeval;
898 if (empty($item->outcomeid)) {
d4795a07 899 $nogradestr = $this->get_lang_string('nograde');
0658afc9 900 } else {
d4795a07 901 $nogradestr = $this->get_lang_string('nooutcome', 'grades');
0658afc9 902 }
6c096a49 903 $itemcell->text .= '<input type="hidden" name="oldgrade_'.$userid.'_'
0658afc9 904 .$item->id.'" value="'.$oldval.'"/>';
064527d6
PS
905 $attributes = array('tabindex' => $tabindices[$item->id]['grade']);
906 $itemcell->text .= html_writer::select($scaleopt, 'grade_'.$userid.'_'.$item->id, $gradeval, array(-1=>$nogradestr), $attributes);;
388234f4 907 } elseif(!empty($scale)) {
4ba9941c 908 $scales = explode(",", $scale->scale);
4ba9941c 909
388234f4 910 // invalid grade if gradeval < 1
99ccfda8 911 if ($gradeval < 1) {
6c096a49 912 $itemcell->text .= $OUTPUT->span('-', "gradevalue$hidden$gradepass");
4ba9941c 913 } else {
653a8648 914 $gradeval = $grade->grade_item->bounded_grade($gradeval); //just in case somebody changes scale
6c096a49 915 $itemcell->text .= $OUTPUT->span($scales[$gradeval-1], "gradevalue$hidden$gradepass");
4ba9941c 916 }
388234f4 917 } else {
918 // no such scale, throw error?
4ba9941c 919 }
79eabc2a 920
bb384a8e 921 } else if ($item->gradetype != GRADE_TYPE_TEXT) { // Value type
936f1350 922 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
76317c73 923 $value = format_float($gradeval, $decimalpoints);
6c096a49
NC
924 $itemcell->text .= '<input type="hidden" name="oldgrade_'.$userid.'_'.$item->id.'" value="'.$value.'" />';
925 $itemcell->text .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade']
319770d7 926 . '" type="text" class="text" title="'. $strgrade .'" name="grade_'
c0c1e7c2 927 .$userid.'_' .$item->id.'" value="'.$value.'" />';
4ba9941c 928 } else {
6c096a49 929 $itemcell->text .= $OUTPUT->span(format_float($gradeval, $decimalpoints), "gradevalue$hidden$gradepass");
4ba9941c 930 }
931 }
932
933
934 // If quickfeedback is on, print an input element
2ca093fa 935 if ($this->get_pref('showquickfeedback') and $grade->is_editable()) {
dc482cfa 936
6c096a49 937 $itemcell->text .= '<input type="hidden" name="oldfeedback_'
29a5680e 938 .$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />';
6c096a49 939 $itemcell->text .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id]['feedback']
be55a047 940 . '" size="6" title="' . $strfeedback . '" type="text" name="feedback_'
29a5680e 941 .$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />';
4ba9941c 942 }
943
78a2d9f0 944 } else { // Not editing
41f22daa 945 $gradedisplaytype = $item->get_displaytype();
e50ce569 946
78a2d9f0 947 // If feedback present, surround grade with feedback tooltip: Open span here
4ba9941c 948
d14ae855 949 if ($item->needsupdate) {
6c096a49 950 $itemcell->text .= $OUTPUT->span(get_string('error'), "gradingerror$hidden$gradepass");
4ba9941c 951 } else {
6c096a49 952 $itemcell->text .= $OUTPUT->span(grade_format_gradevalue($gradeval, $item, true, $gradedisplaytype, null), "gradevalue$hidden$gradepass");
4ba9941c 953 }
4ba9941c 954 }
955
956 if (!empty($this->gradeserror[$item->id][$userid])) {
6c096a49 957 $itemcell->text .= $this->gradeserror[$item->id][$userid];
4ba9941c 958 }
959
6c096a49 960 $itemrow->cells[] = $itemcell;
4ba9941c 961 }
6c096a49 962 $rows[] = $itemrow;
4ba9941c 963 }
4ba9941c 964
6c096a49
NC
965 $rows = $this->get_right_range_row($rows);
966 $rows = $this->get_right_avg_row($rows, true);
967 $rows = $this->get_right_avg_row($rows);
dc482cfa 968
6c096a49
NC
969 return $rows;
970 }
dc482cfa 971
6c096a49
NC
972 /**
973 * Depending on the style of report (fixedstudents vs traditional one-table),
974 * arranges the rows of data in one or two tables, and returns the output of
975 * these tables in HTML
976 * @return string HTML
977 */
978 public function get_grade_table() {
979 global $OUTPUT;
980 $fixedstudents = $this->is_fixed_students();
dc482cfa 981
6c096a49
NC
982 $leftrows = $this->get_left_rows();
983 $rightrows = $this->get_right_rows();
dc482cfa 984
6c096a49 985 $html = '';
dc482cfa 986
203b7e2e 987 if ($fixedstudents) {
319770d7 988 $fixedcolumntable = new html_table();
989 $fixedcolumntable->id = 'fixed_column';
990 $fixedcolumntable->add_class('fixed_grades_column');
71297a3f 991 $fixedcolumntable->bodyclasses = 'leftbody';
6c096a49
NC
992 $fixedcolumntable->data = $leftrows;
993 $html .= $OUTPUT->container($OUTPUT->table($fixedcolumntable), 'left_scroller');
dc482cfa 994
6c096a49
NC
995 $righttable = new html_table();
996 $righttable->id = 'user-grades';
997 $righttable->bodyclasses = array('righttest');
998 $righttable->data = $rightrows;
319770d7 999
6c096a49
NC
1000 $html .= $OUTPUT->container($OUTPUT->table($righttable), 'right_scroller');
1001 } else {
1002 $fulltable = new html_table();
1003 $fulltable->add_classes(array('gradestable', 'flexible', 'boxaligncenter', 'generaltable'));
1004 $fulltable->id = 'user-grades';
1005
1006 // Extract rows from each side (left and right) and collate them into one row each
1007 foreach ($leftrows as $key => $row) {
1008 $row->cells = array_merge($row->cells, $rightrows[$key]->cells);
1009 $fulltable->data[] = $row;
203b7e2e 1010 }
6c096a49
NC
1011 $html .= $OUTPUT->table($fulltable);
1012 }
1013 return $OUTPUT->container($html, 'gradeparent');
1014 }
dc482cfa 1015
6c096a49
NC
1016 /**
1017 * Builds and return the row of icons for the left side of the report.
1018 * It only has one cell that says "Controls"
1019 * @param array $rows The Array of rows for the left part of the report
1020 * @param int $colspan The number of columns this cell has to span
1021 * @return array Array of rows for the left part of the report
1022 */
1023 public function get_left_icons_row($rows=array(), $colspan=1) {
1024 global $USER;
dc482cfa 1025
6c096a49
NC
1026 if ($USER->gradeediting[$this->courseid]) {
1027 $controlsrow = new html_table_row();
1028 $controlsrow->add_class('controls');
1029 $controlscell = new html_table_cell();
1030 $controlscell->add_classes(array('header', 'c0', 'controls'));
1031 $controlscell->colspan = $colspan;
1032 $controlscell->text = $this->get_lang_string('controls','grades');
1033
1034 $controlsrow->cells[] = $controlscell;
1035 $rows[] = $controlsrow;
1036 }
1037 return $rows;
1038 }
dc482cfa 1039
6c096a49
NC
1040 /**
1041 * Builds and return the header for the row of ranges, for the left part of the grader report.
1042 * @param array $rows The Array of rows for the left part of the report
1043 * @param int $colspan The number of columns this cell has to span
1044 * @return array Array of rows for the left part of the report
1045 */
1046 public function get_left_range_row($rows=array(), $colspan=1) {
1047 global $CFG, $USER;
bb9b58a6 1048
6c096a49
NC
1049 if ($this->get_pref('showranges')) {
1050 $rangerow = new html_table_row();
1051 $rangerow->add_classes(array('range', 'r'.$this->rowcount++));
1052 $rangecell = new html_table_cell();
1053 $rangecell->add_classes(array('header', 'c0', 'range'));
1054 $rangecell->colspan = $colspan;
1055 $rangecell->header = true;
1056 $rangecell->scope = 'row';
1057 $rangecell->text = $this->get_lang_string('range','grades');
1058 $rangerow->cells[] = $rangecell;
1059 $rows[] = $rangerow;
1060 }
dc482cfa 1061
6c096a49
NC
1062 return $rows;
1063 }
dc482cfa 1064
6c096a49
NC
1065 /**
1066 * Builds and return the headers for the rows of averages, for the left part of the grader report.
1067 * @param array $rows The Array of rows for the left part of the report
1068 * @param int $colspan The number of columns this cell has to span
1069 * @param bool $groupavg If true, returns the row for group averages, otherwise for overall averages
1070 * @return array Array of rows for the left part of the report
1071 */
1072 public function get_left_avg_row($rows=array(), $colspan=1, $groupavg=false) {
1073 if (!$this->canviewhidden) {
1074 // totals might be affected by hiding, if user can not see hidden grades the aggregations might be altered
1075 // better not show them at all if user can not see all hideen grades
1076 return $rows;
1077 }
dc482cfa 1078
6c096a49
NC
1079 $showaverages = $this->get_pref('showaverages');
1080 $showaveragesgroup = $this->currentgroup && $showaverages;
1081 $straveragegroup = get_string('groupavg', 'grades');
319770d7 1082
6c096a49 1083 if ($groupavg) {
319770d7 1084 if ($showaveragesgroup) {
1085 $groupavgrow = new html_table_row();
1086 $groupavgrow->add_classes(array('groupavg', 'r'.$this->rowcount++));
1087 $groupavgcell = new html_table_cell();
1088 $groupavgcell->add_classes(array('header', 'c0', 'range'));
1089 $groupavgcell->colspan = $colspan;
1090 $groupavgcell->header = true;
1091 $groupavgcell->scope = 'row';
1092 $groupavgcell->text = $straveragegroup;
1093 $groupavgrow->cells[] = $groupavgcell;
6c096a49 1094 $rows[] = $groupavgrow;
203b7e2e 1095 }
6c096a49
NC
1096 } else {
1097 $straverage = get_string('overallaverage', 'grades');
dc482cfa 1098
203b7e2e 1099 if ($showaverages) {
319770d7 1100 $avgrow = new html_table_row();
1101 $avgrow->add_classes(array('avg', 'r'.$this->rowcount++));
1102 $avgcell = new html_table_cell();
1103 $avgcell->add_classes(array('header', 'c0', 'range'));
1104 $avgcell->colspan = $colspan;
1105 $avgcell->header = true;
1106 $avgcell->scope = 'row';
1107 $avgcell->text = $straverage;
1108 $avgrow->cells[] = $avgcell;
6c096a49 1109 $rows[] = $avgrow;
203b7e2e 1110 }
6c096a49 1111 }
203b7e2e 1112
6c096a49
NC
1113 return $rows;
1114 }
dc482cfa 1115
6c096a49
NC
1116 /**
1117 * Builds and return the row of icons when editing is on, for the right part of the grader report.
1118 * @param array $rows The Array of rows for the right part of the report
1119 * @return array Array of rows for the right part of the report
1120 */
1121 public function get_right_icons_row($rows=array()) {
1122 global $USER;
1123 if ($USER->gradeediting[$this->courseid]) {
1124 $iconsrow = new html_table_row();
1125 $iconsrow->add_class('controls');
1126
1127 $showuseridnumber = $this->get_pref('showuseridnumber');
dc482cfa 1128
6c096a49
NC
1129 $columncount = 0;
1130 foreach ($this->gtree->items as $itemid=>$unused) {
1131 // emulate grade element
1132 $item =& $this->gtree->get_item($itemid);
1133
1134 $eid = $this->gtree->get_item_eid($item);
1135 $element = $this->gtree->locate_element($eid);
1136 $itemcell = new html_table_cell();
1137 $itemcell->add_classes(array('controls', 'icons'));
1138 $itemcell->text = $this->get_icons($element);
1139 $iconsrow->cells[] = $itemcell;
1140 }
1141 $rows[] = $iconsrow;
1142 }
1143 return $rows;
dc482cfa 1144 }
1145
18a00359 1146 /**
6c096a49
NC
1147 * Builds and return the row of ranges for the right part of the grader report.
1148 * @param array $rows The Array of rows for the right part of the report
1149 * @return array Array of rows for the right part of the report
18a00359 1150 */
6c096a49
NC
1151 public function get_right_range_row($rows=array()) {
1152 global $OUTPUT;
18a00359 1153
6c096a49
NC
1154 if ($this->get_pref('showranges')) {
1155 $rangesdisplaytype = $this->get_pref('rangesdisplaytype');
1156 $rangesdecimalpoints = $this->get_pref('rangesdecimalpoints');
1157 $rangerow = new html_table_row();
1158 $rangerow->add_classes(array('heading', 'range'));
18a00359 1159
6c096a49
NC
1160 foreach ($this->gtree->items as $itemid=>$unused) {
1161 $item =& $this->gtree->items[$itemid];
1162 $itemcell = new html_table_cell();
1163 $itemcell->header = true;
1164 $itemcell->add_classes(array('header', 'range'));
1165
1166 $hidden = '';
1167 if ($item->is_hidden()) {
1168 $hidden = ' hidden ';
1169 }
1170
1171 $formattedrange = $item->get_formatted_range($rangesdisplaytype, $rangesdecimalpoints);
1172
1173 $itemcell->text = $OUTPUT->container($formattedrange, 'rangevalues'.$hidden);
1174 $rangerow->cells[] = $itemcell;
1175 }
1176 $rows[] = $rangerow;
18a00359 1177 }
6c096a49 1178 return $rows;
18a00359 1179 }
1180
4ba9941c 1181 /**
6c096a49
NC
1182 * Builds and return the row of averages for the right part of the grader report.
1183 * @param array $rows Whether to return only group averages or all averages.
1184 * @param bool $grouponly Whether to return only group averages or all averages.
1185 * @return array Array of rows for the right part of the report
4ba9941c 1186 */
6c096a49
NC
1187 public function get_right_avg_row($rows=array(), $grouponly=false) {
1188 global $CFG, $USER, $DB, $OUTPUT;
4ba9941c 1189
57068674 1190 if (!$this->canviewhidden) {
1191 // totals might be affected by hiding, if user can not see hidden grades the aggregations might be altered
1192 // better not show them at all if user can not see all hideen grades
6c096a49 1193 return $rows;
57068674 1194 }
1195
6c096a49
NC
1196 $showaverages = $this->get_pref('showaverages');
1197 $showaveragesgroup = $this->currentgroup && $showaverages;
1198
5b508a08 1199 $averagesdisplaytype = $this->get_pref('averagesdisplaytype');
1200 $averagesdecimalpoints = $this->get_pref('averagesdecimalpoints');
098042ba 1201 $meanselection = $this->get_pref('meanselection');
1202 $shownumberofgrades = $this->get_pref('shownumberofgrades');
1203
5b508a08 1204 $avghtml = '';
aae94377 1205 $avgcssclass = 'avg';
3446013d 1206
5b508a08 1207 if ($grouponly) {
1208 $straverage = get_string('groupavg', 'grades');
35079f53 1209 $showaverages = $this->currentgroup && $this->get_pref('showaverages');
5b508a08 1210 $groupsql = $this->groupsql;
1211 $groupwheresql = $this->groupwheresql;
319770d7 1212 $groupwheresqlparams = $this->groupwheresql_params;
aae94377 1213 $avgcssclass = 'groupavg';
3446013d 1214 } else {
6308b91c 1215 $straverage = get_string('overallaverage', 'grades');
5b508a08 1216 $showaverages = $this->get_pref('showaverages');
0dba6cb2 1217 $groupsql = "";
1218 $groupwheresql = "";
319770d7 1219 $groupwheresqlparams = array();
3446013d 1220 }
1221
a5b8be62 1222 if ($shownumberofgrades) {
1223 $straverage .= ' (' . get_string('submissions', 'grades') . ') ';
1224 }
1225
f8ae1f86 1226 $totalcount = $this->get_numusers($grouponly);
04259694 1227
319770d7 1228 list($usql, $rolesparams) = $DB->get_in_or_equal(explode(',', $this->gradebookroles), SQL_PARAMS_NAMED, 'grbr0');
d24832f9 1229
5b508a08 1230 if ($showaverages) {
319770d7 1231 $params = array_merge(array('courseid'=>$this->courseid), $rolesparams, $groupwheresqlparams);
04259694 1232
828af11c 1233 // find sums of all grade items in course
1234 $SQL = "SELECT g.itemid, SUM(g.finalgrade) AS sum
d24832f9 1235 FROM {grade_items} gi
1236 JOIN {grade_grades} g ON g.itemid = gi.id
1237 JOIN {user} u ON u.id = g.userid
1238 JOIN {role_assignments} ra ON ra.userid = u.id
828af11c 1239 $groupsql
b50371da 1240 WHERE gi.courseid = :courseid
d24832f9 1241 AND ra.roleid $usql
828af11c 1242 AND ra.contextid ".get_related_contexts_string($this->context)."
1243 AND g.finalgrade IS NOT NULL
883187d0 1244 $groupwheresql
883187d0 1245 GROUP BY g.itemid";
319770d7 1246 $sumarray = array();
d24832f9 1247 if ($sums = $DB->get_records_sql($SQL, $params)) {
ab3444d7 1248 foreach ($sums as $itemid => $csum) {
319770d7 1249 $sumarray[$itemid] = $csum->sum;
7b61efbe 1250 }
4ba9941c 1251 }
66ef0471 1252
883187d0 1253 // MDL-10875 Empty grades must be evaluated as grademin, NOT always 0
1254 // This query returns a count of ungraded grades (NULL finalgrade OR no matching record in grade_grades table)
319770d7 1255 $params = array_merge(array('courseid'=>$this->courseid), $rolesparams, $groupwheresqlparams);
883187d0 1256 $SQL = "SELECT gi.id, COUNT(u.id) AS count
d24832f9 1257 FROM {grade_items} gi
1258 CROSS JOIN {user} u
1259 JOIN {role_assignments} ra ON ra.userid = u.id
1260 LEFT OUTER JOIN {grade_grades} g ON (g.itemid = gi.id AND g.userid = u.id AND g.finalgrade IS NOT NULL)
828af11c 1261 $groupsql
b50371da 1262 WHERE gi.courseid = :courseid
d24832f9 1263 AND ra.roleid $usql
828af11c 1264 AND ra.contextid ".get_related_contexts_string($this->context)."
1265 AND g.id IS NULL
1266 $groupwheresql
883187d0 1267 GROUP BY gi.id";
828af11c 1268
319770d7 1269 $ungradedcounts = $DB->get_records_sql($SQL, $params);
883187d0 1270
6c096a49
NC
1271 $avgrow = new html_table_row();
1272 $avgrow->add_class('avg');
653a8648 1273
b89a70ce 1274 foreach ($this->gtree->items as $itemid=>$unused) {
1275 $item =& $this->gtree->items[$itemid];
1276
67881aa8 1277 if ($item->needsupdate) {
6c096a49
NC
1278 $avgcell = new html_table_cell();
1279 $avgcell->text = $OUTPUT->container(get_string('error'), 'gradingerror');
1280 $avgrow->cells[] = $avgcell;
67881aa8 1281 continue;
1282 }
1283
319770d7 1284 if (!isset($sumarray[$item->id])) {
1285 $sumarray[$item->id] = 0;
33a34cd4 1286 }
883187d0 1287
319770d7 1288 if (empty($ungradedcounts[$itemid])) {
1289 $ungradedcount = 0;
828af11c 1290 } else {
319770d7 1291 $ungradedcount = $ungradedcounts[$itemid]->count;
d1556c09 1292 }
33a34cd4 1293
c2efb501 1294 if ($meanselection == GRADE_REPORT_MEAN_GRADED) {
319770d7 1295 $meancount = $totalcount - $ungradedcount;
33a34cd4 1296 } else { // Bump up the sum by the number of ungraded items * grademin
319770d7 1297 $sumarray[$item->id] += $ungradedcount * $item->grademin;
1298 $meancount = $totalcount;
33a34cd4 1299 }
1300
31a6c06c 1301 $decimalpoints = $item->get_decimals();
41f22daa 1302
bb384a8e 1303 // Determine which display type to use for this average
2cc773f5 1304 if ($USER->gradeediting[$this->courseid]) {
1796708d 1305 $displaytype = GRADE_DISPLAY_TYPE_REAL;
d622930b 1306
e0724506 1307 } else if ($averagesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // no ==0 here, please resave the report and user preferences
d622930b 1308 $displaytype = $item->get_displaytype();
1309
1310 } else {
bb384a8e 1311 $displaytype = $averagesdisplaytype;
1312 }
1313
31a6c06c 1314 // Override grade_item setting if a display preference (not inherit) was set for the averages
e0724506 1315 if ($averagesdecimalpoints == GRADE_REPORT_PREFERENCE_INHERIT) {
d622930b 1316 $decimalpoints = $item->get_decimals();
1317
1318 } else {
5b508a08 1319 $decimalpoints = $averagesdecimalpoints;
1320 }
1321
319770d7 1322 if (!isset($sumarray[$item->id]) || $meancount == 0) {
6c096a49
NC
1323 $avgcell = new html_table_cell();
1324 $avgcell->text = '-';
1325 $avgrow->cells[] = $avgcell;
1326
4ba9941c 1327 } else {
319770d7 1328 $sum = $sumarray[$item->id];
1329 $avgradeval = $sum/$meancount;
d622930b 1330 $gradehtml = grade_format_gradevalue($avgradeval, $item, true, $displaytype, $decimalpoints);
bb384a8e 1331
098042ba 1332 $numberofgrades = '';
098042ba 1333 if ($shownumberofgrades) {
319770d7 1334 $numberofgrades = " ($meancount)";
098042ba 1335 }
1336
6c096a49
NC
1337 $avgcell = new html_table_cell();
1338 $avgcell->text = $gradehtml.$numberofgrades;
1339 $avgrow->cells[] = $avgcell;
80fb1cf9 1340 }
9ecd4386 1341 }
6c096a49 1342 $rows[] = $avgrow;
9ecd4386 1343 }
6c096a49 1344 return $rows;
9ecd4386 1345 }
4ba9941c 1346
1347 /**
1348 * Given a grade_category, grade_item or grade_grade, this function
1349 * figures out the state of the object and builds then returns a div
1350 * with the icons needed for the grader report.
1351 *
1352 * @param object $object
4ba9941c 1353 * @return string HTML
1354 */
d24832f9 1355 protected function get_icons($element) {
319770d7 1356 global $CFG, $USER, $OUTPUT;
4ba9941c 1357
2cc773f5 1358 if (!$USER->gradeediting[$this->courseid]) {
1359 return '<div class="grade_icons" />';
79eabc2a 1360 }
4ba9941c 1361
2cc773f5 1362 // Init all icons
319770d7 1363 $editicon = '';
dc482cfa 1364
1365 if ($element['type'] != 'categoryitem' && $element['type'] != 'courseitem') {
319770d7 1366 $editicon = $this->gtree->get_edit_icon($element, $this->gpr);
dc482cfa 1367 }
1368
319770d7 1369 $editcalculationicon = '';
1370 $showhideicon = '';
1371 $lockunlockicon = '';
4ba9941c 1372
a5b8be62 1373 if (has_capability('moodle/grade:manage', $this->context)) {
4ba9941c 1374
a5b8be62 1375 if ($this->get_pref('showcalculations')) {
319770d7 1376 $editcalculationicon = $this->gtree->get_calculation_icon($element, $this->gpr);
a5b8be62 1377 }
1378
1379 if ($this->get_pref('showeyecons')) {
319770d7 1380 $showhideicon = $this->gtree->get_hiding_icon($element, $this->gpr);
a5b8be62 1381 }
4ba9941c 1382
a5b8be62 1383 if ($this->get_pref('showlocks')) {
319770d7 1384 $lockunlockicon = $this->gtree->get_locking_icon($element, $this->gpr);
a5b8be62 1385 }
4ba9941c 1386 }
1387
319770d7 1388 return $OUTPUT->container($editicon.$editcalculationicon.$showhideicon.$lockunlockicon, 'grade_icons');
4faf5f99 1389 }
1390
1391 /**
1392 * Given a category element returns collapsing +/- icon if available
1393 * @param object $object
1394 * @return string HTML
1395 */
d24832f9 1396 protected function get_collapsing_icon($element) {
5d3b9994 1397 global $OUTPUT;
4faf5f99 1398
319770d7 1399 $icon = '';
2cc773f5 1400 // If object is a category, display expand/contract icon
384960dd 1401 if ($element['type'] == 'category') {
2cc773f5 1402 // Load language strings
319770d7 1403 $strswitchminus = $this->get_lang_string('aggregatesonly', 'grades');
1404 $strswitchplus = $this->get_lang_string('gradesonly', 'grades');
1405 $strswitchwhole = $this->get_lang_string('fullmode', 'grades');
384960dd 1406
8ae8bf8a 1407 $url = new moodle_url($this->gpr->get_return_url(null, array('target'=>$element['eid'], 'sesskey'=>sesskey())));
48b5d8f3 1408
384960dd 1409 if (in_array($element['object']->id, $this->collapsed['aggregatesonly'])) {
8ae8bf8a
PS
1410 $url->param('action', 'switch_plus');
1411 $icon = $OUTPUT->action_icon($url, $strswitchplus, 't/switch_plus', array('class'=>'iconsmall'));
1412
1413 } else if (in_array($element['object']->id, $this->collapsed['gradesonly'])) {
1414 $url->param('action', 'switch_whole');
1415 $icon = $OUTPUT->action_icon($url, $strswitchwhole, 't/switch_whole', array('class'=>'iconsmall'));
b5d0cafc 1416 $contractexpandicon->image->src = $OUTPUT->pix_url('t/switch_whole');
8ae8bf8a
PS
1417
1418 } else {
1419 $url->param('action', 'switch_minus');
1420 $icon = $OUTPUT->action_icon($url, $strswitchminus, 't/switch_minus', array('class'=>'iconsmall'));
2cc773f5 1421 }
4ba9941c 1422 }
319770d7 1423 return $icon;
4ba9941c 1424 }
48b5d8f3 1425
1426 /**
1427 * Processes a single action against a category, grade_item or grade.
1428 * @param string $target eid ({type}{id}, e.g. c4 for category4)
1429 * @param string $action Which action to take (edit, delete etc...)
1430 * @return
1431 */
d24832f9 1432 public function process_action($target, $action) {
4faf5f99 1433 // TODO: this code should be in some grade_tree static method
48b5d8f3 1434 $targettype = substr($target, 0, 1);
1435 $targetid = substr($target, 1);
4faf5f99 1436 // TODO: end
1437
1438 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) {
1439 $collapsed = unserialize($collapsed);
1440 } else {
384960dd 1441 $collapsed = array('aggregatesonly' => array(), 'gradesonly' => array());
4faf5f99 1442 }
1443
48b5d8f3 1444 switch ($action) {
384960dd 1445 case 'switch_minus': // Add category to array of aggregatesonly
1446 if (!in_array($targetid, $collapsed['aggregatesonly'])) {
1447 $collapsed['aggregatesonly'][] = $targetid;
4faf5f99 1448 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));
1449 }
48b5d8f3 1450 break;
4faf5f99 1451
384960dd 1452 case 'switch_plus': // Remove category from array of aggregatesonly, and add it to array of gradesonly
1453 $key = array_search($targetid, $collapsed['aggregatesonly']);
4faf5f99 1454 if ($key !== false) {
384960dd 1455 unset($collapsed['aggregatesonly'][$key]);
4faf5f99 1456 }
384960dd 1457 if (!in_array($targetid, $collapsed['gradesonly'])) {
1458 $collapsed['gradesonly'][] = $targetid;
1459 }
1460 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));
48b5d8f3 1461 break;
384960dd 1462 case 'switch_whole': // Remove the category from the array of collapsed cats
1463 $key = array_search($targetid, $collapsed['gradesonly']);
1464 if ($key !== false) {
1465 unset($collapsed['gradesonly'][$key]);
1466 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));
1467 }
4faf5f99 1468
384960dd 1469 break;
48b5d8f3 1470 default:
1471 break;
1472 }
1473
1474 return true;
1475 }
03fcc729 1476
795b745a 1477 /**
1478 * Returns whether or not to display fixed students column.
1479 * Includes a browser check, because IE6 doesn't support the scrollbar.
1480 *
1481 * @return bool
1482 */
1483 public function is_fixed_students() {
1484 global $USER, $CFG;
03fcc729 1485 return empty($USER->screenreader) && $CFG->grade_report_fixedstudents &&
1486 (check_browser_version('MSIE', '7.0') ||
795b745a 1487 check_browser_version('Firefox', '2.0') ||
1488 check_browser_version('Gecko', '2006010100') ||
1489 check_browser_version('Camino', '1.0') ||
1490 check_browser_version('Opera', '6.0') ||
03fcc729 1491 check_browser_version('Safari', '2.0'));
795b745a 1492 }
6c096a49
NC
1493
1494 /**
1495 * Refactored function for generating HTML of sorting links with matching arrows.
1496 * Returns an array with 'studentname' and 'idnumber' as keys, with HTML ready
1497 * to inject into a table header cell.
1498 * @return array An associative array of HTML sorting links+arrows
1499 */
1500 public function get_sort_arrows() {
1501 global $OUTPUT;
1502 $arrows = array();
1503
1504 $strsortasc = $this->get_lang_string('sortasc', 'grades');
1505 $strsortdesc = $this->get_lang_string('sortdesc', 'grades');
1506 $strfirstname = $this->get_lang_string('firstname');
1507 $strlastname = $this->get_lang_string('lastname');
1508
0f4c64b7
PS
1509 $firstlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'firstname')), $strfirstname);
1510 $lastlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'lastname')), $strlastname);
1511 $idnumberlink = html_writer::link(new moodle_url($this->baseurl, array('sortitemid'=>'idnumber')), get_string('idnumber'));
6c096a49 1512
0f4c64b7 1513 $arrows['studentname'] = $lastlink;
6c096a49
NC
1514
1515 if ($this->sortitemid === 'lastname') {
1516 if ($this->sortorder == 'ASC') {
1517 $arrows['studentname'] .= print_arrow('up', $strsortasc, true);
1518 } else {
1519 $arrows['studentname'] .= print_arrow('down', $strsortdesc, true);
1520 }
1521 }
1522
0f4c64b7 1523 $arrows['studentname'] .= ' ' . $firstlink;
6c096a49
NC
1524
1525 if ($this->sortitemid === 'firstname') {
1526 if ($this->sortorder == 'ASC') {
1527 $arrows['studentname'] .= print_arrow('up', $strsortasc, true);
1528 } else {
1529 $arrows['studentname'] .= print_arrow('down', $strsortdesc, true);
1530 }
1531 }
1532
0f4c64b7 1533 $arrows['idnumber'] = $idnumberlink;
6c096a49
NC
1534
1535 if ('idnumber' == $this->sortitemid) {
1536 if ($this->sortorder == 'ASC') {
1537 $arrows['idnumber'] .= print_arrow('up', $strsortasc, true);
1538 } else {
1539 $arrows['idnumber'] .= print_arrow('down', $strsortdesc, true);
1540 }
1541 }
1542
1543 return $arrows;
1544 }
4ba9941c 1545}
6c3ef410 1546