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