MDL-10452 - start quiz UI is confusing if you have quiz:preview capability. Change...
[moodle.git] / grade / report / grader / lib.php
CommitLineData
4ba9941c 1<?php // $Id$
2/**
3 * File in which the grader_report class is defined.
4 * @package gradebook
5 */
6
eea6690a 7require_once($CFG->dirroot . '/grade/report/lib.php');
4ba9941c 8require_once($CFG->libdir.'/tablelib.php');
4ba9941c 9
10/**
11 * Class providing an API for the grader report building and displaying.
eea6690a 12 * @uses grade_report
4ba9941c 13 * @package gradebook
14 */
eea6690a 15class grade_report_grader extends grade_report {
4ba9941c 16 /**
17 * The final grades.
18 * @var array $finalgrades
19 */
20 var $finalgrades;
21
22 /**
23 * The grade items.
24 * @var array $items
25 */
26 var $items;
27
28 /**
29 * Array of errors for bulk grades updating.
30 * @var array $gradeserror
31 */
32 var $gradeserror = array();
33
4ba9941c 34//// SQL-RELATED
35
4ba9941c 36 /**
37 * The id of the grade_item by which this report will be sorted.
38 * @var int $sortitemid
39 */
40 var $sortitemid;
41
42 /**
43 * Sortorder used in the SQL selections.
44 * @var int $sortorder
45 */
46 var $sortorder;
47
48 /**
49 * An SQL fragment affecting the search for users.
50 * @var string $userselect
51 */
52 var $userselect;
53
54//// GROUP VARIABLES (including SQL)
55
56 /**
57 * The current group being displayed.
58 * @var int $currentgroup
59 */
60 var $currentgroup;
61
62 /**
63 * A HTML select element used to select the current group.
64 * @var string $group_selector
65 */
66 var $group_selector;
67
68 /**
69 * An SQL fragment used to add linking information to the group tables.
70 * @var string $groupsql
71 */
72 var $groupsql;
73
74 /**
75 * An SQL constraint to append to the queries used by this object to build the report.
76 * @var string $groupwheresql
77 */
78 var $groupwheresql;
79
80
4ba9941c 81 /**
82 * Constructor. Sets local copies of user preferences and initialises grade_tree.
83 * @param int $courseid
eea6690a 84 * @param string $context
85 * @param int $page The current page being viewed (when report is paged)
86 * @param int $sortitemid The id of the grade_item by which to sort the table
4ba9941c 87 */
88 function grade_report_grader($courseid, $context, $page=null, $sortitemid=null) {
89 global $CFG;
eea6690a 90 parent::grade_report($courseid, $context, $page);
4ba9941c 91
4ba9941c 92 $this->sortitemid = $sortitemid;
93
4ba9941c 94 // base url for sorting by first/last name
936f1350 95 $this->baseurl = 'report.php?id='.$this->courseid.'&amp;perpage='.$this->get_pref('studentsperpage')
e5161d0c 96 .'&amp;report=grader&amp;page='.$this->page;
4ba9941c 97 //
936f1350 98 $this->pbarurl = 'report.php?id='.$this->courseid.'&amp;perpage='.$this->get_pref('studentsperpage')
e5161d0c 99 .'&amp;report=grader&amp;';
4ba9941c 100
936f1350 101 if ($this->get_pref('showgroups')) {
4ba9941c 102 $this->setup_groups();
103 }
104
105 $this->setup_sortitemid();
106 }
107
4ba9941c 108 /**
109 * Processes the data sent by the form (grades and feedbacks).
110 * @var array $data
111 * @return bool Success or Failure (array of errors).
112 */
113 function process_data($data) {
114 // always initialize all arrays
115 $queue = array();
116
117 foreach ($data as $varname => $postedvalue) {
118 // this is a bit tricky - we have to first load all grades into memory,
119 // check if changed and only then start updating the final grades because
120 // columns might depend one on another - the result would be overriden calculated and category grades
121
122 $needsupdate = false;
123 $note = false; // TODO implement note??
124
125 // skip, not a grade nor feedback
79eabc2a 126 if (strpos($varname, 'grade') === 0) {
4ba9941c 127 $data_type = 'grade';
79eabc2a 128 } else if (strpos($varname, 'feedback') === 0) {
4ba9941c 129 $data_type = 'feedback';
130 } else {
131 continue;
132 }
133
134 $gradeinfo = explode("_", $varname);
135
136 $userid = clean_param($gradeinfo[1], PARAM_INT);
137 $itemid = clean_param($gradeinfo[2], PARAM_INT);
138
139 if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here!
140 error('Incorrect grade item id');
141 }
142
143 // Pre-process grade
144 if ($data_type == 'grade') {
4256a134 145 $feedback = false;
146 $feedbackformat = false;
4ba9941c 147 if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
148 if ($postedvalue == -1) { // -1 means no grade
149 $finalgrade = null;
150 } else {
151 $finalgrade = (float)$postedvalue;
152 }
153 } else {
154 if ($postedvalue == '') { // empty string means no grade
155 $finalgrade = null;
156 } else {
eea6690a 157 $finalgrade = $this->format_grade($postedvalue);
4ba9941c 158 }
159 }
4ba9941c 160 if (!is_null($finalgrade) and ($finalgrade < $grade_item->grademin or $finalgrade > $grade_item->grademax)) {
161 $this->gradeserror[$grade_item->id][$userid] = 'outofrange'; //TODO: localize
162 // another possiblity is to use bounded number instead
163 continue;
164 }
79eabc2a 165
166 } else if ($data_type == 'feedback') {
4256a134 167 $finalgrade = false;
79eabc2a 168 $trimmed = trim($postedvalue);
169 if (empty($trimmed)) {
170 $postedvalue = NULL;
171 }
4ba9941c 172 }
173
174 // Get the grade object to compare old value with new value
175 if ($grade = grade_grades::fetch(array('userid'=>$userid, 'itemid'=>$grade_item->id))) {
176 if ($data_type == 'feedback') {
79eabc2a 177 if ($text = $grade->load_text()) {
178 if ($text->feedback !== $postedvalue) {
179 $feedback = $postedvalue;
180 $feedbackformat = $text->feedbackformat; // keep original format or else we would have to do proper conversion (but it is not always possible)
181 $needsupdate = true;
182 }
183 } else {
184 $feedback = $postedvalue;
eea6690a 185 $feedbackformat = FORMAT_MOODLE; // this is the default format option everywhere else
79eabc2a 186 $needsupdate = true;
4ba9941c 187 }
79eabc2a 188
189 } else if ($data_type == 'grade') {
4ba9941c 190 if (!is_null($grade->finalgrade)) {
191 $grade->finalgrade = (float)$grade->finalgrade;
192 }
79eabc2a 193 if ($grade->finalgrade !== $finalgrade) {
4ba9941c 194 $needsupdate = true;
195 }
196 }
4256a134 197 } else { // The grade_grades record doesn't yet exist
198 $needsupdate = true;
4ba9941c 199 }
200
201 // we must not update all grades, only changed ones - we do not want to mark everything as overriden
202 if ($needsupdate) {
203 $gradedata = new object();
204 $gradedata->grade_item = $grade_item;
205 $gradedata->userid = $userid;
206 $gradedata->note = $note;
207 $gradedata->finalgrade = $finalgrade;
208 $gradedata->feedback = $feedback;
209 $gradedata->feedbackformat = $feedbackformat;
210
211 $queue[] = $gradedata;
212 }
213 }
214
215 // now we update the new final grade for each changed grade
216 foreach ($queue as $gradedata) {
217 $gradedata->grade_item->update_final_grade($gradedata->userid, $gradedata->finalgrade, 'gradebook',
218 $gradedata->note, $gradedata->feedback, $gradedata->feedbackformat);
219 }
220
221 return true;
222 }
223
224 /**
225 * Sets up this object's group variables, mainly to restrict the selection of users to display.
226 */
227 function setup_groups() {
228 global $CFG;
229
230 /// find out current groups mode
231 $course = get_record('course', 'id', $this->courseid);
232 $groupmode = $course->groupmode;
233 ob_start();
234 $this->currentgroup = setup_and_print_groups($course, $groupmode, $this->baseurl);
235 $this->group_selector = ob_get_clean();
236
237 // update paging after group
238 $this->baseurl .= 'group='.$this->currentgroup.'&amp;';
239 $this->pbarurl .= 'group='.$this->currentgroup.'&amp;';
240
241 if ($this->currentgroup) {
242 $this->groupsql = " LEFT JOIN {$CFG->prefix}groups_members gm ON gm.userid = u.id ";
243 $this->groupwheresql = " AND gm.groupid = $this->currentgroup ";
244 }
245 }
246
247 /**
248 * Setting the sort order, this depends on last state
249 * all this should be in the new table class that we might need to use
250 * for displaying grades.
251 */
252 function setup_sortitemid() {
253 if ($this->sortitemid) {
254 if (!isset($SESSION->gradeuserreport->sort)) {
255 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
256 } else {
257 // this is the first sort, i.e. by last name
258 if (!isset($SESSION->gradeuserreport->sortitemid)) {
259 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
260 } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) {
261 // same as last sort
262 if ($SESSION->gradeuserreport->sort == 'ASC') {
263 $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC';
264 } else {
265 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
266 }
267 } else {
268 $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC';
269 }
270 }
271 $SESSION->gradeuserreport->sortitemid = $this->sortitemid;
272 } else {
273 // not requesting sort, use last setting (for paging)
274
275 if (isset($SESSION->gradeuserreport->sortitemid)) {
276 $this->sortitemid = $SESSION->gradeuserreport->sortitemid;
277 }
278 if (isset($SESSION->gradeuserreport->sort)) {
279 $this->sortorder = $SESSION->gradeuserreport->sort;
280 } else {
281 $this->sortorder = 'ASC';
282 }
283 }
284 }
285
4ba9941c 286 /**
287 * pulls out the userids of the users to be display, and sort them
288 * the right outer join is needed because potentially, it is possible not
289 * to have the corresponding entry in grade_grades table for some users
290 * this is check for user roles because there could be some users with grades
291 * but not supposed to be displayed
292 */
293 function load_users() {
294 global $CFG;
295
296 if (is_numeric($this->sortitemid)) {
297 $sql = "SELECT u.id, u.firstname, u.lastname
298 FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN
299 {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid)
300 LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
301 $this->groupsql
302 WHERE ra.roleid in ($this->gradebookroles)
303 $this->groupwheresql
304 AND ra.contextid ".get_related_contexts_string($this->context)."
305 ORDER BY g.finalgrade $this->sortorder";
936f1350 306 $this->users = get_records_sql($sql, $this->get_pref('studentsperpage') * $this->page,
307 $this->get_pref('studentsperpage'));
4ba9941c 308 } else {
309 // default sort
310 // get users sorted by lastname
311 $this->users = get_role_users(@implode(',', $CFG->gradebookroles), $this->context, false,
312 'u.id, u.firstname, u.lastname', 'u.'.$this->sortitemid .' '. $this->sortorder,
936f1350 313 false, $this->page * $this->get_pref('studentsperpage'), $this->get_pref('studentsperpage'),
e5161d0c 314 $this->currentgroup);
4ba9941c 315 // need to cut users down by groups
316
317 }
318
319 if (empty($this->users)) {
320 $this->userselect = '';
321 $this->users = array();
322 } else {
323 $this->userselect = 'AND g.userid in ('.implode(',', array_keys($this->users)).')';
324 }
325
326 return $this->users;
327 }
328
329 /**
330 * Fetches and returns a count of all the users that will be shows on this page.
331 * @return int Count of users
332 */
333 function get_numusers() {
334 global $CFG;
335 $countsql = "SELECT COUNT(DISTINCT u.id)
336 FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN
337 {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid)
338 LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
339 $this->groupsql
340 WHERE ra.roleid in ($this->gradebookroles)
341 $this->groupwheresql
342 AND ra.contextid ".get_related_contexts_string($this->context);
343 return count_records_sql($countsql);
344 }
345
346 /**
347 * we supply the userids in this query, and get all the grades
348 * pulls out all the grades, this does not need to worry about paging
349 */
350 function load_final_grades() {
351 global $CFG;
352
bd6c9ddb 353 $sql = "SELECT g.id, g.itemid, g.userid, g.finalgrade, g.hidden, g.locked, g.locktime, g.overridden, gt.feedback, gt.feedbackformat
4ba9941c 354 FROM {$CFG->prefix}grade_items gi,
355 {$CFG->prefix}grade_grades g
356 LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid
357 WHERE g.itemid = gi.id
358 AND gi.courseid = $this->courseid $this->userselect";
359
360 if ($grades = get_records_sql($sql)) {
361 foreach ($grades as $grade) {
362 $this->finalgrades[$grade->userid][$grade->itemid] = $grade;
363 }
364 }
365 }
366
367 /**
368 * Builds and returns a div with on/off toggles.
369 * @return string HTML code
370 */
371 function get_toggles_html() {
372 global $USER;
373 $html = '<div id="grade-report-toggles">';
374 if ($USER->gradeediting) {
375 $html .= $this->print_toggle('eyecons', true);
376 $html .= $this->print_toggle('locks', true);
377 $html .= $this->print_toggle('calculations', true);
378 }
379
380 $html .= $this->print_toggle('grandtotals', true);
381 $html .= $this->print_toggle('groups', true);
382 $html .= $this->print_toggle('scales', true);
383 $html .= '</div>';
384 return $html;
385 }
386
387 /**
388 * Shortcut function for printing the grader report toggles.
389 * @param string $type The type of toggle
390 * @param bool $return Whether to return the HTML string rather than printing it
391 * @return void
392 */
393 function print_toggle($type, $return=false) {
394 global $CFG;
395
396 $icons = array('eyecons' => 'hide',
397 'calculations' => 'calc',
398 'locks' => 'lock',
399 'grandtotals' => 'sigma');
400
401 $pref_name = 'grade_report_show' . $type;
eea6690a 402 $show_pref = get_user_preferences($pref_name, $CFG->$pref_name);
4ba9941c 403
404 $strshow = get_string('show' . $type, 'grades');
405 $strhide = get_string('hide' . $type, 'grades');
406
407 $show_hide = 'show';
408 $toggle_action = 1;
409
410 if ($show_pref) {
411 $show_hide = 'hide';
412 $toggle_action = 0;
413 }
414
415 if (array_key_exists($type, $icons)) {
416 $image_name = $icons[$type];
417 } else {
418 $image_name = $type;
419 }
420
421 $string = ${'str' . $show_hide};
422
423 $img = '<img src="'.$CFG->pixpath.'/t/'.$image_name.'.gif" class="iconsmall" alt="'
424 .$string.'" title="'.$string.'" />'. "\n";
425
426 $retval = '<div class="gradertoggle">' . $img . '<a href="' . $this->baseurl . "&amp;toggle=$toggle_action&amp;toggle_type=$type\">"
427 . $string . '</a></div>';
428
429 if ($return) {
430 return $retval;
431 } else {
432 echo $retval;
433 }
434 }
435
436 /**
437 * Builds and returns the HTML code for the headers.
438 * @return string $headerhtml
439 */
440 function get_headerhtml() {
441 global $CFG, $USER;
442
443 $strsortasc = get_string('sortasc', 'grades');
444 $strsortdesc = get_string('sortdesc', 'grades');
445 if ($this->sortitemid === 'lastname') {
446 if ($this->sortorder == 'ASC') {
447 $lastarrow = print_arrow('up', $strsortasc, true);
448 } else {
449 $lastarrow = print_arrow('down', $strsortdesc, true);
450 }
451 } else {
452 $lastarrow = '';
453 }
454
455 if ($this->sortitemid === 'firstname') {
456 if ($this->sortorder == 'ASC') {
457 $firstarrow = print_arrow('up', $strsortasc, true);
458 } else {
459 $firstarrow = print_arrow('down', $strsortdesc, true);
460 }
461 } else {
462 $firstarrow = '';
463 }
464 // Prepare Table Headers
465 $headerhtml = '';
466
467 $numrows = count($this->gtree->levels);
468
469 foreach ($this->gtree->levels as $key=>$row) {
470 if ($key == 0) {
471 // do not diplay course grade category
472 // continue;
473 }
474
475 $headerhtml .= '<tr class="heading">';
476
477 if ($key == $numrows - 1) {
478 $headerhtml .= '<th class="user"><a href="'.$this->baseurl.'&amp;sortitemid=firstname">Firstname</a> ' //TODO: localize
479 . $firstarrow. '/ <a href="'.$this->baseurl.'&amp;sortitemid=lastname">Lastname </a>'. $lastarrow .'</th>';
480 } else {
481 $headerhtml .= '<td class="topleft">&nbsp;</td>';
482 }
483
484 foreach ($row as $element) {
485 $eid = $element['eid'];
486 $object = $element['object'];
487 $type = $element['type'];
488
489 if (!empty($element['colspan'])) {
490 $colspan = 'colspan="'.$element['colspan'].'"';
491 } else {
492 $colspan = '';
493 }
494
495 if (!empty($element['depth'])) {
496 $catlevel = ' catlevel'.$element['depth'];
497 } else {
498 $catlevel = '';
499 }
500
501
502 if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') {
503 $headerhtml .= '<td class="'.$type.$catlevel.'" '.$colspan.'>&nbsp;</td>';
504 } else if ($type == 'category') {
505 $headerhtml .= '<td class="category'.$catlevel.'" '.$colspan.'>'.$element['object']->get_name();
506
507 // Print icons
508 if ($USER->gradeediting) {
509 $headerhtml .= $this->get_icons($element);
510 }
511
512 $headerhtml .= '</td>';
513 } else {
514 if ($element['object']->id == $this->sortitemid) {
515 if ($this->sortorder == 'ASC') {
516 $arrow = print_arrow('up', $strsortasc, true);
517 } else {
518 $arrow = print_arrow('down', $strsortdesc, true);
519 }
520 } else {
521 $arrow = '';
522 }
523
524 $dimmed = '';
525 if ($element['object']->is_hidden()) {
526 $dimmed = ' dimmed_text ';
527 }
528
529 if ($object->itemtype == 'mod') {
530 $icon = '<img src="'.$CFG->modpixpath.'/'.$object->itemmodule.'/icon.gif" class="icon" alt="'
531 .get_string('modulename', $object->itemmodule).'"/>';
532 } else if ($object->itemtype == 'manual') {
533 //TODO: add manual grading icon
534 $icon = '<img src="'.$CFG->pixpath.'/t/edit.gif" class="icon" alt="'.get_string('manualgrade', 'grades')
535 .'"/>';
536 }
537
538
539 $headerhtml .= '<th class="'.$type.$catlevel.$dimmed.'"><a href="'.$this->baseurl.'&amp;sortitemid='
540 . $element['object']->id .'">'. $element['object']->get_name()
541 . '</a>' . $arrow;
542
543 $headerhtml .= $this->get_icons($element) . '</th>';
544
545 $this->items[$element['object']->sortorder] =& $element['object'];
546 }
547
548 }
549
550 $headerhtml .= '</tr>';
551 }
552 return $headerhtml;
553 }
554
555 /**
556 * Builds and return the HTML rows of the table (grades headed by student).
557 * @return string HTML
558 */
559 function get_studentshtml() {
560 global $CFG, $USER;
561 $studentshtml = '';
562 $strfeedback = get_string("feedback");
18effef4 563 $gradetabindex = 1;
564 $feedbacktabindex = 16380; // The maximum number of tabindices on 1 page is 32767
4ba9941c 565
566 foreach ($this->users as $userid => $user) {
567 // Student name and link
568 $studentshtml .= '<tr><th class="user"><a href="' . $CFG->wwwroot . '/user/view.php?id='
569 . $user->id . '">' . fullname($user) . '</a></th>';
570 foreach ($this->items as $item) {
571
572 if (isset($this->finalgrades[$userid][$item->id])) {
573 $gradeval = $this->finalgrades[$userid][$item->id]->finalgrade;
574 $grade = new grade_grades($this->finalgrades[$userid][$item->id], false);
575 $grade->feedback = $this->finalgrades[$userid][$item->id]->feedback;
bd6c9ddb 576 $grade->feedbackformat = $this->finalgrades[$userid][$item->id]->feedbackformat;
4ba9941c 577
578 } else {
579 $gradeval = null;
580 $grade = new grade_grades(array('userid' => $userid, 'itemid' => $item->id), false);
581 $grade->feedback = '';
582 }
583
584 if ($grade->is_overridden()) {
585 $studentshtml .= '<td class="overridden">';
586 } else {
587 $studentshtml .= '<td>';
588 }
589
590 // emulate grade element
591 $grade->courseid = $this->courseid;
592 $grade->grade_item = $item; // this may speedup is_hidden() and other grade_grades methods
593 $element = array ('eid'=>'g'.$grade->id, 'object'=>$grade, 'type'=>'grade');
594
595 // Do not show any icons if no grade (no record in DB to match)
79eabc2a 596 // TODO: change edit/hide/etc. links to use itemid and userid to allow creating of new grade objects
4ba9941c 597 if (!empty($grade->id)) {
598 $studentshtml .= $this->get_icons($element);
599 }
600
601 // if in editting mode, we need to print either a text box
602 // or a drop down (for scales)
603
604 // grades in item of type grade category or course are not directly editable
605 if ($USER->gradeediting) {
606 // We need to retrieve each grade_grade object from DB in order to
607 // know if they are hidden/locked
608
609 if ($item->scaleid) {
610 if ($scale = get_record('scale', 'id', $item->scaleid)) {
611 $scales = explode(",", $scale->scale);
612 // reindex because scale is off 1
613 $i = 0;
614 foreach ($scales as $scaleoption) {
615 $i++;
616 $scaleopt[$i] = $scaleoption;
617 }
618
936f1350 619 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
4ba9941c 620 $studentshtml .= choose_from_menu($scaleopt, 'grade_'.$userid.'_'.$item->id,
18effef4 621 $gradeval, get_string('nograde'), '', -1, true, false, $gradetabindex++);
4ba9941c 622 } elseif ($scale = get_record('scale', 'id', $item->scaleid)) {
623 $scales = explode(",", $scale->scale);
624
625 // invalid grade if gradeval < 1
626 if ((int) $gradeval < 1) {
627 $studentshtml .= '-';
628 } else {
629 $studentshtml .= $scales[$gradeval-1];
630 }
631 } else {
632 // no such scale, throw error?
633 }
634 }
79eabc2a 635
636 } else if ($item->gradetype != GRADE_TYPE_TEXT) {
936f1350 637 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
18effef4 638 $studentshtml .= '<input size="6" tabindex="' . $gradetabindex++ . '" type="text" name="grade_'.$userid.'_'
eea6690a 639 .$item->id.'" value="'.$this->get_grade_clean($gradeval).'"/>';
4ba9941c 640 } else {
eea6690a 641 $studentshtml .= $this->get_grade_clean($gradeval);
4ba9941c 642 }
643 }
644
645
646 // If quickfeedback is on, print an input element
936f1350 647 if ($this->get_pref('quickfeedback') and $grade->is_editable()) {
648 if ($this->get_pref('quickgrading')) {
4ba9941c 649 $studentshtml .= '<br />';
650 }
18effef4 651 $studentshtml .= '<input tabindex="' . $feedbacktabindex++ . '" size="6" type="text" name="feedback_'
652 .$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '"/>';
4ba9941c 653 }
654
4ba9941c 655 } else {
656 // If feedback present, surround grade with feedback tooltip
657 if (!empty($grade->feedback)) {
bd6c9ddb 658 if ($grade->feedbackformat == 1) {
659 $overlib = "return overlib('$grade->feedback', CAPTION, '$strfeedback');";
660 } else {
661 $overlib = "return overlib('" . s(ltrim($grade->feedback)) . "', FULLHTML);";
662 }
663
664 $studentshtml .= '<span onmouseover="' . $overlib . '" onmouseout="return nd();">';
4ba9941c 665 }
666
667 // finalgrades[$userid][$itemid] could be null because of the outer join
668 // in this case it's different than a 0
669 if ($item->scaleid) {
670 if ($scale = get_record('scale', 'id', $item->scaleid)) {
671 $scales = explode(",", $scale->scale);
672
673 // invalid grade if gradeval < 1
674 if ((int) $gradeval < 1) {
675 $studentshtml .= '-';
676 } else {
677 $studentshtml .= $scales[$gradeval-1];
678 }
679 } else {
680 // no such scale, throw error?
681 }
682 } else {
683 if (is_null($gradeval)) {
684 $studentshtml .= '-';
685 } else {
eea6690a 686 $studentshtml .= $this->get_grade_clean($gradeval);
4ba9941c 687 }
688 }
689 if (!empty($grade->feedback)) {
690 $studentshtml .= '</span>';
691 }
692 }
693
694 if (!empty($this->gradeserror[$item->id][$userid])) {
695 $studentshtml .= $this->gradeserror[$item->id][$userid];
696 }
697
698 $studentshtml .= '</td>' . "\n";
699 }
700 $studentshtml .= '</tr>';
701 }
702 return $studentshtml;
703 }
704
705 /**
706 * Builds and return the HTML rows of the table (grades headed by student).
707 * @return string HTML
708 */
709 function get_groupsumhtml() {
710 global $CFG;
711
712 $groupsumhtml = '';
713
936f1350 714 if ($this->currentgroup && $this->get_pref('showgroups')) {
4ba9941c 715
716 /** SQL for finding group sum */
717 $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum
718 FROM {$CFG->prefix}grade_items gi LEFT JOIN
719 {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN
720 {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN
721 {$CFG->prefix}role_assignments ra ON u.id = ra.userid
722 $this->groupsql
723 WHERE gi.courseid = $this->courseid
724 $this->groupwheresql
725 AND ra.roleid in ($this->gradebookroles)
726 AND ra.contextid ".get_related_contexts_string($this->context)."
727 GROUP BY g.itemid";
728
729 $groupsum = array();
730 $sums = get_records_sql($SQL);
731 foreach ($sums as $itemid => $csum) {
732 $groupsum[$itemid] = $csum;
733 }
734
735 $groupsumhtml = '<tr><th>Group total</th>';
736 foreach ($this->items as $item) {
737 if (!isset($groupsum[$item->id])) {
738 $groupsumhtml .= '<td>-</td>';
739 } else {
740 $sum = $groupsum[$item->id];
eea6690a 741 $groupsumhtml .= '<td>'.$this->get_grade_clean($sum->sum).'</td>';
4ba9941c 742 }
743 }
744 $groupsumhtml .= '</tr>';
745 }
746 return $groupsumhtml;
747 }
748
e5161d0c 749 /**
750 * Builds and return the HTML row of column totals.
751 * @return string HTML
752 */
4ba9941c 753 function get_gradesumhtml() {
754 global $CFG;
755
756 $gradesumhtml = '';
936f1350 757 if ($this->get_pref('showgrandtotals')) {
4ba9941c 758
759 /** SQL for finding the SUM grades of all visible users ($CFG->gradebookroles) */
760
761 $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum
762 FROM {$CFG->prefix}grade_items gi LEFT JOIN
763 {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN
764 {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN
765 {$CFG->prefix}role_assignments ra ON u.id = ra.userid
766 WHERE gi.courseid = $this->courseid
767 AND ra.roleid in ($this->gradebookroles)
768 AND ra.contextid ".get_related_contexts_string($this->context)."
769 GROUP BY g.itemid";
770
771 $classsum = array();
772 $sums = get_records_sql($SQL);
773 foreach ($sums as $itemid => $csum) {
774 $classsum[$itemid] = $csum;
775 }
776
777 $gradesumhtml = '<tr><th>Total</th>';
778 foreach ($this->items as $item) {
779 if (!isset($classsum[$item->id])) {
780 $gradesumhtml .= '<td>-</td>';
781 } else {
782 $sum = $classsum[$item->id];
eea6690a 783 $gradesumhtml .= '<td>'.$this->get_grade_clean($sum->sum).'</td>';
4ba9941c 784 }
785 }
786 $gradesumhtml .= '</tr>';
787 }
788 return $gradesumhtml;
789 }
790
e5161d0c 791 /**
792 * Builds and return the HTML row of scales for each column (i.e. range).
793 * @return string HTML
794 */
4ba9941c 795 function get_scalehtml() {
796 $scalehtml = '';
936f1350 797 if ($this->get_pref('showscales')) {
4ba9941c 798 $scalehtml = '<tr><td>'.get_string('range','grades').'</td>';
799 foreach ($this->items as $item) {
eea6690a 800 $scalehtml .= '<td>'. $this->get_grade_clean($item->grademin).'-'. $this->get_grade_clean($item->grademax).'</td>';
4ba9941c 801 }
802 $scalehtml .= '</tr>';
803 }
804 return $scalehtml;
805 }
806
807 /**
808 * Given a grade_category, grade_item or grade_grade, this function
809 * figures out the state of the object and builds then returns a div
810 * with the icons needed for the grader report.
811 *
812 * @param object $object
813 * @param array $icons An array of icon names that this function is explicitly requested to print, regardless of settings
814 * @param bool $limit If true, use the $icons array as the only icons that will be printed. If false, use it to exclude these icons.
815 * @return string HTML
816 */
817 function get_icons($element, $icons=null, $limit=true) {
818 global $CFG;
819 global $USER;
820
821 // Load language strings
822 $stredit = get_string("edit");
823 $streditcalculation= get_string("editcalculation", 'grades');
824 $strfeedback = get_string("feedback");
825 $strmove = get_string("move");
826 $strmoveup = get_string("moveup");
827 $strmovedown = get_string("movedown");
828 $strmovehere = get_string("movehere");
829 $strcancel = get_string("cancel");
830 $stredit = get_string("edit");
831 $strdelete = get_string("delete");
832 $strhide = get_string("hide");
833 $strshow = get_string("show");
834 $strlock = get_string("lock", 'grades');
835 $strswitch_minus = get_string("contract", 'grades');
836 $strswitch_plus = get_string("expand", 'grades');
837 $strunlock = get_string("unlock", 'grades');
838
839 // Prepare container div
840 $html = '<div class="grade_icons">';
841
842 // Prepare reference variables
843 $eid = $element['eid'];
844 $object = $element['object'];
845 $type = $element['type'];
846
847 // Add mock attributes in case the object is not of the right type
848 if ($type != 'grade') {
849 $object->feedback = '';
850 }
851
4ba9941c 852 $overlib = '';
853 if (!empty($object->feedback)) {
bd6c9ddb 854 if (empty($object->feedbackformat) || $object->feedbackformat != 1) {
855 $function = "return overlib('$object->feedback', CAPTION, '$strfeedback');";
856 } else {
857 $function = "return overlib('" . s(ltrim($object->feedback)) . "', FULLHTML);";
858 }
859 $overlib = 'onmouseover="' . $function . '" onmouseout="return nd();"';
4ba9941c 860 }
861
79eabc2a 862 // Prepare image strings
863 $edit_icon = '';
864 if ($object->is_editable()) {
865 if ($type == 'category') {
e662a443 866 $edit_icon = '<a href="'. GRADE_EDIT_URL . '/category.php?courseid='.$object->courseid.'&amp;id='.$object->id.'">'
79eabc2a 867 . '<img src="'.$CFG->pixpath.'/t/edit.gif" class="iconsmall" alt="'
868 . $stredit.'" title="'.$stredit.'" /></a>'. "\n";
869 } else if ($type == 'item' or $type == 'categoryitem' or $type == 'courseitem'){
e662a443 870 $edit_icon = '<a href="'. GRADE_EDIT_URL . '/item.php?courseid='.$object->courseid.'&amp;id='.$object->id.'">'
79eabc2a 871 . '<img src="'.$CFG->pixpath.'/t/edit.gif" class="iconsmall" alt="'
872 . $stredit.'" title="'.$stredit.'" /></a>'. "\n";
873 } else if ($type == 'grade' and ($object->is_editable() or empty($object->id))) {
874 // TODO: change link to use itemid and userid to allow creating of new grade objects
e662a443 875 $edit_icon = '<a href="'. GRADE_EDIT_URL . '/grade.php?courseid='.$object->courseid.'&amp;id='.$object->id.'">'
79eabc2a 876 . '<img ' . $overlib . ' src="'.$CFG->pixpath.'/t/edit.gif"'
877 . 'class="iconsmall" alt="' . $stredit.'" title="'.$stredit.'" /></a>'. "\n";
878 }
879 }
4ba9941c 880
95d6df77 881 $edit_calculation_icon = '';
882 if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
883 // show calculation icon only when calculation possible
884 if (!$object->is_normal_item() and ($object->gradetype == GRADE_TYPE_SCALE or $object->gradetype == GRADE_TYPE_VALUE)) {
e662a443 885 $edit_calculation_icon = '<a href="'. GRADE_EDIT_URL . '/calculation.php?courseid='.$object->courseid.'&amp;id='.$object->id.'">'
95d6df77 886 . '<img src="'.$CFG->pixpath.'/t/calc.gif" class="iconsmall" alt="'
887 . $streditcalculation.'" title="'.$streditcalculation.'" /></a>'. "\n";
888 }
889 }
4ba9941c 890
891 // Prepare Hide/Show icon state
892 $hide_show = 'hide';
893 if ($object->is_hidden()) {
894 $hide_show = 'show';
895 }
896
897 $show_hide_icon = '<a href="report.php?report=grader&amp;target='.$eid
898 . "&amp;action=$hide_show" . $this->gtree->commonvars . "\">\n"
899 . '<img src="'.$CFG->pixpath.'/t/'.$hide_show.'.gif" class="iconsmall" alt="'
900 . ${'str' . $hide_show}.'" title="'.${'str' . $hide_show}.'" /></a>'. "\n";
901
902 // Prepare lock/unlock string
903 $lock_unlock = 'lock';
904 if ($object->is_locked()) {
905 $lock_unlock = 'unlock';
906 }
907
908 // Print lock/unlock icon
909
910 $lock_unlock_icon = '<a href="report.php?report=grader&amp;target='.$eid
911 . "&amp;action=$lock_unlock" . $this->gtree->commonvars . "\">\n"
912 . '<img src="'.$CFG->pixpath.'/t/'.$lock_unlock.'.gif" class="iconsmall" alt="'
913 . ${'str' . $lock_unlock}.'" title="'.${'str' . $lock_unlock}.'" /></a>'. "\n";
914
915 // Prepare expand/contract string
916 $expand_contract = 'switch_minus'; // Default: expanded
eea6690a 917 $state = get_user_preferences('grade_category_' . $object->id, GRADE_CATEGORY_EXPANDED);
4ba9941c 918 if ($state == GRADE_CATEGORY_CONTRACTED) {
919 $expand_contract = 'switch_plus';
920 }
921
922 $contract_expand_icon = '<a href="report.php?report=grader&amp;target=' . $eid
923 . "&amp;action=$expand_contract" . $this->gtree->commonvars . "\">\n"
924 . '<img src="'.$CFG->pixpath.'/t/'.$expand_contract.'.gif" class="iconsmall" alt="'
925 . ${'str' . $expand_contract}.'" title="'.${'str' . $expand_contract}.'" /></a>'. "\n";
926
927 // If an array of icon names is given, return only these in the order they are given
928 if (!empty($icons) && is_array($icons)) {
929 $new_html = '';
930
931 foreach ($icons as $icon_name) {
4ba9941c 932 if ($limit) {
933 $new_html .= ${$icon_name . '_icon'};
934 } else {
935 ${'show_' . $icon_name} = false;
936 }
937 }
938 if ($limit) {
939 return $new_html;
940 } else {
941 $html .= $new_html;
942 }
943 }
944
945 // Icons shown when edit mode is on
946 if ($USER->gradeediting) {
947 // Edit icon (except for grade_grades)
79eabc2a 948 if ($edit_icon) {
949 $html .= $edit_icon;
4ba9941c 950 }
951
952 // Calculation icon for items and categories
936f1350 953 if ($this->get_pref('showcalculations')) {
4ba9941c 954 $html .= $edit_calculation_icon;
955 }
956
936f1350 957 if ($this->get_pref('showeyecons')) {
4ba9941c 958 $html .= $show_hide_icon;
959 }
960
936f1350 961 if ($this->get_pref('showlocks')) {
4ba9941c 962 $html .= $lock_unlock_icon;
963 }
964
965 // If object is a category, display expand/contract icon
936f1350 966 if (get_class($object) == 'grade_category' && $this->get_pref('aggregationview') == GRADER_REPORT_AGGREGATION_VIEW_COMPACT) {
4ba9941c 967 $html .= $contract_expand_icon;
968 }
969 } else { // Editing mode is off
970 }
971
972 return $html . '</div>';
973 }
974}
975?>