3 * File in which the grader_report class is defined.
7 require_once($CFG->dirroot
. '/grade/report/lib.php');
8 require_once($CFG->libdir
.'/tablelib.php');
11 * Class providing an API for the grader report building and displaying.
15 class grade_report_grader
extends grade_report
{
18 * @var array $finalgrades
29 * Array of errors for bulk grades updating.
30 * @var array $gradeserror
32 var $gradeserror = array();
37 * The id of the grade_item by which this report will be sorted.
38 * @var int $sortitemid
43 * Sortorder used in the SQL selections.
49 * An SQL fragment affecting the search for users.
50 * @var string $userselect
55 * List of collapsed categories from user preference
56 * @var array $collapsed
61 * A count of the rows, used for css classes.
67 * Constructor. Sets local copies of user preferences and initialises grade_tree.
68 * @param int $courseid
69 * @param object $gpr grade plugin return tracking object
70 * @param string $context
71 * @param int $page The current page being viewed (when report is paged)
72 * @param int $sortitemid The id of the grade_item by which to sort the table
74 function grade_report_grader($courseid, $gpr, $context, $page=null, $sortitemid=null) {
76 parent
::grade_report($courseid, $gpr, $context, $page);
78 // load collapsed settings for this report
79 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) {
80 $this->collapsed
= unserialize($collapsed);
82 $this->collapsed
= array('aggregatesonly' => array(), 'gradesonly' => array());
85 if (empty($CFG->enableoutcomes
)) {
88 $nooutcomes = get_user_preferences('grade_report_shownooutcomes');
91 // Grab the grade_tree for this course
92 $this->gtree
= new grade_tree($this->courseid
, true, $this->get_pref('aggregationposition'), $this->collapsed
, $nooutcomes);
94 $this->sortitemid
= $sortitemid;
96 // base url for sorting by first/last name
97 $studentsperpage = $this->get_pref('studentsperpage');
101 if (!empty($studentsperpage)) {
102 $perpage = '&perpage='.$studentsperpage;
103 $curpage = '&page='.$this->page
;
105 $this->baseurl
= 'index.php?id='.$this->courseid
. $perpage.$curpage.'&';
107 $this->pbarurl
= 'index.php?id='.$this->courseid
.$perpage.'&';
109 // Setup groups if requested
110 if ($this->get_pref('showgroups')) {
111 $this->setup_groups();
114 $this->setup_sortitemid();
118 * Processes the data sent by the form (grades and feedbacks).
120 * @return bool Success or Failure (array of errors).
122 function process_data($data) {
124 if (!has_capability('moodle/grade:override', $this->context
)) {
128 // always initialize all arrays
130 foreach ($data as $varname => $postedvalue) {
132 $needsupdate = false;
133 $note = false; // TODO implement note??
135 // skip, not a grade nor feedback
136 if (strpos($varname, 'grade') === 0) {
137 $data_type = 'grade';
138 } else if (strpos($varname, 'feedback') === 0) {
139 $data_type = 'feedback';
144 $gradeinfo = explode("_", $varname);
145 $userid = clean_param($gradeinfo[1], PARAM_INT
);
146 $itemid = clean_param($gradeinfo[2], PARAM_INT
);
148 $oldvalue = $data->{'old'.$varname};
150 // was change requested?
151 if ($oldvalue == $postedvalue) {
155 if (!$grade_item = grade_item
::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid
))) { // we must verify course id here!
156 error('Incorrect grade item id');
160 if ($data_type == 'grade') {
162 $feedbackformat = false;
163 if ($grade_item->gradetype
== GRADE_TYPE_SCALE
) {
164 if ($postedvalue == -1) { // -1 means no grade
167 $finalgrade = $postedvalue;
170 $finalgrade = unformat_float($postedvalue);
173 } else if ($data_type == 'feedback') {
175 $trimmed = trim($postedvalue);
176 if (empty($trimmed)) {
179 $feedback = stripslashes($postedvalue);
183 $grade_item->update_final_grade($userid, $finalgrade, 'gradebook', $note, $feedback);
191 * Setting the sort order, this depends on last state
192 * all this should be in the new table class that we might need to use
193 * for displaying grades.
195 function setup_sortitemid() {
199 if ($this->sortitemid
) {
200 if (!isset($SESSION->gradeuserreport
->sort
)) {
201 $this->sortorder
= $SESSION->gradeuserreport
->sort
= 'DESC';
203 // this is the first sort, i.e. by last name
204 if (!isset($SESSION->gradeuserreport
->sortitemid
)) {
205 $this->sortorder
= $SESSION->gradeuserreport
->sort
= 'DESC';
206 } else if ($SESSION->gradeuserreport
->sortitemid
== $this->sortitemid
) {
208 if ($SESSION->gradeuserreport
->sort
== 'ASC') {
209 $this->sortorder
= $SESSION->gradeuserreport
->sort
= 'DESC';
211 $this->sortorder
= $SESSION->gradeuserreport
->sort
= 'ASC';
214 $this->sortorder
= $SESSION->gradeuserreport
->sort
= 'DESC';
217 $SESSION->gradeuserreport
->sortitemid
= $this->sortitemid
;
219 // not requesting sort, use last setting (for paging)
221 if (isset($SESSION->gradeuserreport
->sortitemid
)) {
222 $this->sortitemid
= $SESSION->gradeuserreport
->sortitemid
;
224 if (isset($SESSION->gradeuserreport
->sort
)) {
225 $this->sortorder
= $SESSION->gradeuserreport
->sort
;
227 $this->sortorder
= 'ASC';
233 * pulls out the userids of the users to be display, and sort them
234 * the right outer join is needed because potentially, it is possible not
235 * to have the corresponding entry in grade_grades table for some users
236 * this is check for user roles because there could be some users with grades
237 * but not supposed to be displayed
239 function load_users() {
242 if (is_numeric($this->sortitemid
)) {
243 $sql = "SELECT u.id, u.firstname, u.lastname
244 FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN
245 {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid)
246 LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid
248 WHERE ra.roleid in ($this->gradebookroles)
250 AND ra.contextid ".get_related_contexts_string($this->context
)."
251 ORDER BY g.finalgrade $this->sortorder";
252 $this->users
= get_records_sql($sql, $this->get_pref('studentsperpage') * $this->page
,
253 $this->get_pref('studentsperpage'));
256 // get users sorted by lastname
258 // If lastname or firstname is given as sortitemid, add the other name (firstname or lastname respectively) as second sort param
260 if ($this->sortitemid
== 'lastname') {
261 $sort2 = ', u.firstname ' . $this->sortorder
;
262 } elseif ($this->sortitemid
== 'firstname') {
263 $sort2 = ', u.lastname ' . $this->sortorder
;
266 $this->users
= get_role_users($this->gradebookroles
, $this->context
, false,
267 'u.id, u.firstname, u.lastname', 'u.'.$this->sortitemid
.' '. $this->sortorder
. $sort2,
268 false, $this->page
* $this->get_pref('studentsperpage'), $this->get_pref('studentsperpage'),
269 $this->currentgroup
);
270 // need to cut users down by groups
274 if (empty($this->users
)) {
275 $this->userselect
= '';
276 $this->users
= array();
278 $this->userselect
= 'AND g.userid in ('.implode(',', array_keys($this->users
)).')';
285 * Fetches and returns a count of all the users that will be shown on this page.
286 * @param bool $groups Whether to apply groupsql
287 * @return int Count of users
289 function get_numusers($groups=true) {
292 $countsql = "SELECT COUNT(DISTINCT u.id)
293 FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN
294 {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid)
295 LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid ";
297 $countsql .= $this->groupsql
;
299 $countsql .= " WHERE ra.roleid in ($this->gradebookroles) ";
301 $countsql .= $this->groupwheresql
;
303 $countsql .= " AND ra.contextid ".get_related_contexts_string($this->context
);
304 return count_records_sql($countsql);
308 * we supply the userids in this query, and get all the grades
309 * pulls out all the grades, this does not need to worry about paging
311 function load_final_grades() {
314 // please note that we must fetch all grade_grades fields if we want to contruct grade_grade object from it!
315 $sql = "SELECT g.*, gt.feedback, gt.feedbackformat, gi.grademin, gi.grademax
316 FROM {$CFG->prefix}grade_items gi,
317 {$CFG->prefix}grade_grades g
318 LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid
319 WHERE g.itemid = gi.id AND gi.courseid = $this->courseid $this->userselect";
321 if ($grades = get_records_sql($sql)) {
322 foreach ($grades as $grade) {
323 $this->finalgrades
[$grade->userid
][$grade->itemid
] = $grade;
329 * Builds and returns a div with on/off toggles.
330 * @return string HTML code
332 function get_toggles_html() {
335 $html = '<div id="grade-report-toggles">';
336 if ($USER->gradeediting
[$this->courseid
]) {
337 if (has_capability('moodle/grade:manage', $this->context
) or has_capability('moodle/grade:hide', $this->context
)) {
338 $html .= $this->print_toggle('eyecons', true);
340 if (has_capability('moodle/grade:manage', $this->context
)
341 or has_capability('moodle/grade:lock', $this->context
)
342 or has_capability('moodle/grade:unlock', $this->context
)) {
343 $html .= $this->print_toggle('locks', true);
345 if (has_capability('moodle/grade:manage', $this->context
)) {
346 $html .= $this->print_toggle('calculations', true);
350 $html .= $this->print_toggle('averages', true);
352 if (has_capability('moodle/grade:viewall', $this->context
)
353 and has_capability('moodle/site:accessallgroups', $this->context
)
354 and $course_has_groups = true) { // TODO replace that last condition with proper check
355 $html .= $this->print_toggle('groups', true);
358 $html .= $this->print_toggle('ranges', true);
359 if (!empty($CFG->enableoutcomes
)) {
360 $html .= $this->print_toggle('nooutcomes', true);
367 * Shortcut function for printing the grader report toggles.
368 * @param string $type The type of toggle
369 * @param bool $return Whether to return the HTML string rather than printing it
372 function print_toggle($type, $return=false) {
375 $icons = array('eyecons' => 't/hide.gif',
376 'calculations' => 't/calc.gif',
377 'locks' => 't/lock.gif',
378 'averages' => 't/sigma.gif',
379 'nooutcomes' => 't/outcomes.gif');
381 $pref_name = 'grade_report_show' . $type;
383 if (array_key_exists($pref_name, $CFG)) {
384 $show_pref = get_user_preferences($pref_name, $CFG->$pref_name);
386 $show_pref = get_user_preferences($pref_name);
389 $strshow = $this->get_lang_string('show' . $type, 'grades');
390 $strhide = $this->get_lang_string('hide' . $type, 'grades');
400 if (array_key_exists($type, $icons)) {
401 $image_name = $icons[$type];
403 $image_name = "t/$type.gif";
406 $string = $
{'str' . $show_hide};
408 $img = '<img src="'.$CFG->pixpath
.'/'.$image_name.'" class="iconsmall" alt="'
409 .$string.'" title="'.$string.'" />'. "\n";
411 $retval = '<div class="gradertoggle">' . $img . '<a href="' . $this->baseurl
. "&toggle=$toggle_action&toggle_type=$type\">"
412 . $string . '</a></div>';
422 * Builds and returns the HTML code for the headers.
423 * @return string $headerhtml
425 function get_headerhtml() {
428 $strsortasc = $this->get_lang_string('sortasc', 'grades');
429 $strsortdesc = $this->get_lang_string('sortdesc', 'grades');
430 $strfirstname = $this->get_lang_string('firstname');
431 $strlastname = $this->get_lang_string('lastname');
433 if ($this->sortitemid
=== 'lastname') {
434 if ($this->sortorder
== 'ASC') {
435 $lastarrow = print_arrow('up', $strsortasc, true);
437 $lastarrow = print_arrow('down', $strsortdesc, true);
443 if ($this->sortitemid
=== 'firstname') {
444 if ($this->sortorder
== 'ASC') {
445 $firstarrow = print_arrow('up', $strsortasc, true);
447 $firstarrow = print_arrow('down', $strsortdesc, true);
452 // Prepare Table Headers
455 $numrows = count($this->gtree
->levels
);
457 $columns_to_unset = array();
460 foreach ($this->gtree
->levels
as $key=>$row) {
463 // do not display course grade category
467 $headerhtml .= '<tr class="heading r'.$this->rowcount++
.'">';
469 if ($key == $numrows - 1) {
470 $headerhtml .= '<th class="header c'.$columncount++
.' user" scope="col"><a href="'.$this->baseurl
.'&sortitemid=firstname">'
471 . $strfirstname . '</a> ' //TODO: localize
472 . $firstarrow. '/ <a href="'.$this->baseurl
.'&sortitemid=lastname">' . $strlastname . '</a>'. $lastarrow .'</th>';
474 $headerhtml .= '<td class="cell c'.$columncount++
.' topleft"> </td>';
477 foreach ($row as $columnkey => $element) {
479 if (isset($element['object']->id
)) {
480 $sort_link = $this->baseurl
.'&sortitemid=' . $element['object']->id
;
483 $eid = $element['eid'];
484 $object = $element['object'];
485 $type = $element['type'];
486 $categorystate = @$element['categorystate'];
488 $iteminstance = null;
490 $columnclass = 'c' . $columncount++
;
491 if (!empty($element['colspan'])) {
492 $colspan = 'colspan="'.$element['colspan'].'"';
498 if (!empty($element['depth'])) {
499 $catlevel = ' catlevel'.$element['depth'];
504 // Element is a filler
505 if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') {
506 $headerhtml .= '<th class="'.$columnclass.' '.$type.$catlevel.'" '.$colspan.' scope="col"> </th>';
508 // Element is a category
509 else if ($type == 'category') {
510 $headerhtml .= '<th class="header '. $columnclass.' category'.$catlevel.'" '.$colspan.' scope="col">'
511 . $element['object']->get_name();
512 $headerhtml .= $this->get_collapsing_icon($element);
515 if ($USER->gradeediting
[$this->courseid
]) {
516 $headerhtml .= $this->get_icons($element);
519 $headerhtml .= '</th>';
521 // Element is a grade_item
523 $itemmodule = $element['object']->itemmodule
;
524 $iteminstance = $element['object']->iteminstance
;
526 if ($element['object']->id
== $this->sortitemid
) {
527 if ($this->sortorder
== 'ASC') {
528 $arrow = $this->get_sort_arrow('up', $sort_link);
530 $arrow = $this->get_sort_arrow('down', $sort_link);
533 $arrow = $this->get_sort_arrow('move', $sort_link);
537 if ($element['object']->is_hidden()) {
538 $dimmed = ' dimmed_text ';
541 if ($object->itemtype
== 'mod') {
542 $icon = '<img src="'.$CFG->modpixpath
.'/'.$object->itemmodule
.'/icon.gif" class="icon" alt="'
543 .$this->get_lang_string('modulename', $object->itemmodule
).'"/>';
544 } else if ($object->itemtype
== 'manual') {
545 //TODO: add manual grading icon
546 $icon = '<img src="'.$CFG->pixpath
.'/t/edit.gif" class="icon" alt="'
547 .$this->get_lang_string('manualgrade', 'grades') .'"/>';
550 $headerlink = $this->get_module_link($element['object']->get_name(), $itemmodule, $iteminstance);
551 $headerhtml .= '<th class="header '.$columnclass.' '.$type.$catlevel.$dimmed.'" scope="col">'. $headerlink . $arrow;
552 $headerhtml .= $this->get_icons($element) . '</th>';
554 $this->items
[$element['object']->sortorder
] =& $element['object'];
559 $headerhtml .= '</tr>';
565 * Builds and return the HTML rows of the table (grades headed by student).
566 * @return string HTML
568 function get_studentshtml() {
571 $strfeedback = $this->get_lang_string("feedback");
572 $strgrade = $this->get_lang_string('grade');
574 $showuserimage = $this->get_pref('showuserimage');
575 $numusers = count($this->users
);
577 // Preload scale objects for items with a scaleid
579 $tabindices = array();
580 foreach ($this->items
as $item) {
581 if (!empty($item->scaleid
)) {
582 $scales_list .= "$item->scaleid,";
584 $tabindices[$item->id
]['grade'] = $gradetabindex;
585 $tabindices[$item->id
]['feedback'] = $gradetabindex +
$numusers;
586 $gradetabindex +
= $numusers * 2;
588 $scales_array = array();
590 if (!empty($scales_list)) {
591 $scales_list = substr($scales_list, 0, -1);
592 $scales_array = get_records_list('scale', 'id', $scales_list);
595 foreach ($this->users
as $userid => $user) {
597 // Student name and link
599 if ($showuserimage) {
600 $user_pic = '<div class="userpic">' . print_user_picture($user->id
, $this->courseid
, true, 0, true) . '</div>';
603 $studentshtml .= '<tr class="r'.$this->rowcount++
.'"><th class="header c'.$columncount++
.' user" scope="row">' . $user_pic
604 . '<a href="' . $CFG->wwwroot
. '/user/view.php?id='
605 . $user->id
. '">' . fullname($user) . '</a></th>';
607 foreach ($this->items
as $itemid=>$item) {
608 // Get the decimal points preference for this item
609 $decimalpoints = $this->get_pref('decimalpoints', $item->id
);
611 if (isset($this->finalgrades
[$userid][$item->id
])) {
612 $gradeval = $this->finalgrades
[$userid][$item->id
]->finalgrade
;
613 $grade = new grade_grade($this->finalgrades
[$userid][$item->id
], false);
614 $grade->feedback
= stripslashes_safe($this->finalgrades
[$userid][$item->id
]->feedback
);
615 $grade->feedbackformat
= $this->finalgrades
[$userid][$item->id
]->feedbackformat
;
619 $grade = new grade_grade(array('userid'=>$userid, 'itemid'=>$item->id
), false);
620 $grade->feedback
= '';
623 $grade->courseid
= $this->courseid
;
624 $grade->grade_item
=& $this->items
[$itemid]; // this speedsup is_hidden() and other grade_grade methods
626 // emulate grade element
627 $eid = $this->gtree
->get_grade_eid($grade);
628 $element = array('eid'=>$eid, 'object'=>$grade, 'type'=>'grade');
630 if ($grade->is_overridden()) {
631 $studentshtml .= '<td class="overridden cell c'.$columncount++
.'">';
633 $studentshtml .= '<td class="cell c'.$columncount++
.'">';
636 if ($grade->is_excluded()) {
637 $studentshtml .= get_string('excluded', 'grades'); // TODO: improve visual representation of excluded grades
640 // Do not show any icons if no grade (no record in DB to match)
641 if (!$item->needsupdate
and $USER->gradeediting
[$this->courseid
]) {
642 $studentshtml .= $this->get_icons($element);
645 // if in editting mode, we need to print either a text box
646 // or a drop down (for scales)
647 // grades in item of type grade category or course are not directly editable
648 if ($item->needsupdate
) {
649 $studentshtml .= '<span class="gradingerror">'.get_string('error').'</span>';
651 } else if ($USER->gradeediting
[$this->courseid
]) {
652 // We need to retrieve each grade_grade object from DB in order to
653 // know if they are hidden/locked
655 if ($item->scaleid
&& !empty($scales_array[$item->scaleid
])) {
656 $scale = $scales_array[$item->scaleid
];
658 $scales = explode(",", $scale->scale
);
659 // reindex because scale is off 1
661 foreach ($scales as $scaleoption) {
663 $scaleopt[$i] = $scaleoption;
666 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
667 $oldval = empty($gradeval) ?
-1 : $gradeval;
668 if (empty($item->outcomeid
)) {
669 $nogradestr = $this->get_lang_string('nograde');
671 $nogradestr = $this->get_lang_string('nooutcome', 'grades');
673 $studentshtml .= '<input type="hidden" name="oldgrade_'.$userid.'_'
674 .$item->id
.'" value="'.$oldval.'"/>';
675 $studentshtml .= choose_from_menu($scaleopt, 'grade_'.$userid.'_'.$item->id
,
676 $gradeval, $nogradestr, '', '-1',
677 true, false, $tabindices[$item->id
]['grade']);
678 } elseif(!empty($scale)) {
679 $scales = explode(",", $scale->scale
);
681 // invalid grade if gradeval < 1
682 if ((int) $gradeval < 1) {
683 $studentshtml .= '-';
685 $studentshtml .= $scales[$gradeval-1];
688 // no such scale, throw error?
691 } else if ($item->gradetype
!= GRADE_TYPE_TEXT
) { // Value type
692 if ($this->get_pref('quickgrading') and $grade->is_editable()) {
693 $value = format_float($gradeval, $decimalpoints);
694 $studentshtml .= '<input type="hidden" name="oldgrade_'.$userid.'_'.$item->id
.'" value="'.$value.'" />';
695 $studentshtml .= '<input size="6" tabindex="' . $tabindices[$item->id
]['grade']
696 . '" type="text" title="'. $strgrade .'" name="grade_'
697 .$userid.'_' .$item->id
.'" value="'.$value.'" />';
699 $studentshtml .= format_float($gradeval, $decimalpoints);
704 // If quickfeedback is on, print an input element
705 if ($this->get_pref('quickfeedback') and $grade->is_editable()) {
706 if ($this->get_pref('quickgrading')) {
707 $studentshtml .= '<br />';
709 $studentshtml .= '<input type="hidden" name="oldfeedback_'
710 .$userid.'_'.$item->id
.'" value="' . s($grade->feedback
) . '" />';
711 $studentshtml .= '<input class="quickfeedback" tabindex="' . $tabindices[$item->id
]['feedback']
712 . '" size="6" title="' . $strfeedback . '" type="text" name="feedback_'
713 .$userid.'_'.$item->id
.'" value="' . s($grade->feedback
) . '" />';
717 // Percentage format if specified by user (check each item for a set preference)
718 $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id
);
721 $grademin = $item->grademin
;
722 $grademax = $item->grademax
;
724 if ($gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE
) {
725 if (!is_null($gradeval)) {
726 $gradeval = grade_to_percentage($gradeval, $grademin, $grademax);
731 // If feedback present, surround grade with feedback tooltip
732 if (!empty($grade->feedback
)) {
733 if ($grade->feedbackformat
== 1) {
734 $overlib = "return overlib('" . s(ltrim($grade->feedback
)) . "', FULLHTML);";
736 $overlib = "return overlib('" . ($grade->feedback
) . "', CAPTION, '$strfeedback');";
739 $studentshtml .= '<span onmouseover="' . $overlib . '" onmouseout="return nd();">';
742 if ($item->needsupdate
) {
743 $studentshtml .= '<span class="gradingerror">'.get_string('error').'</span>';
745 } else if ($gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER
) {
746 $letters = grade_report
::get_grade_letters();
747 if (!is_null($gradeval)) {
748 $studentshtml .= grade_grade
::get_letter($letters, $gradeval, $grademin, $grademax);
750 } else if ($item->scaleid
&& !empty($scales_array[$item->scaleid
])
751 && $gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL
) {
752 $scale = $scales_array[$item->scaleid
];
753 $scales = explode(",", $scale->scale
);
755 // invalid grade if gradeval < 1
756 if ((int) $gradeval < 1) {
757 $studentshtml .= '-';
759 $studentshtml .= $scales[$gradeval-1];
762 if (is_null($gradeval)) {
763 $studentshtml .= '-';
765 $studentshtml .= format_float($gradeval, $decimalpoints). $percentsign;
768 if (!empty($grade->feedback
)) {
769 $studentshtml .= '</span>';
773 if (!empty($this->gradeserror
[$item->id
][$userid])) {
774 $studentshtml .= $this->gradeserror
[$item->id
][$userid];
777 $studentshtml .= '</td>' . "\n";
779 $studentshtml .= '</tr>';
781 return $studentshtml;
785 * Builds and return the HTML row of column totals.
786 * @param bool $grouponly Whether to return only group averages or all averages.
787 * @return string HTML
789 function get_avghtml($grouponly=false) {
792 $averagesdisplaytype = $this->get_pref('averagesdisplaytype');
793 $averagesdecimalpoints = $this->get_pref('averagesdecimalpoints');
794 $meanselection = $this->get_pref('meanselection');
795 $shownumberofgrades = $this->get_pref('shownumberofgrades');
798 $avgcssclass = 'avg';
801 $straverage = get_string('groupavg', 'grades');
802 $showaverages = $this->currentgroup
&& $this->get_pref('showgroups');
803 $groupsql = $this->groupsql
;
804 $groupwheresql = $this->groupwheresql
;
805 $avgcssclass = 'groupavg';
807 $straverage = get_string('overallaverage', 'grades');
808 $showaverages = $this->get_pref('showaverages');
810 $groupwheresql = null;
813 $totalcount = $this->get_numusers($grouponly);
817 // the first join on user is needed for groupsql
818 $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum
819 FROM {$CFG->prefix}grade_items gi LEFT JOIN
820 {$CFG->prefix}grade_grades g ON gi.id = g.itemid LEFT JOIN
821 {$CFG->prefix}user u ON g.userid = u.id
823 WHERE gi.courseid = $this->courseid
826 SELECT DISTINCT(u.id)
827 FROM {$CFG->prefix}user u LEFT JOIN
828 {$CFG->prefix}role_assignments ra ON u.id = ra.userid
829 WHERE ra.roleid in ($this->gradebookroles)
830 AND ra.contextid ".get_related_contexts_string($this->context
)."
833 $sum_array = array();
834 if ($sums = get_records_sql($SQL)) {
835 foreach ($sums as $itemid => $csum) {
836 $sum_array[$itemid] = $csum->sum
;
840 $avghtml = '<tr class="' . $avgcssclass . ' r'.$this->rowcount++
.'"><th class="header c0" scope="row">'.$straverage.'</th>';
843 foreach ($this->items
as $item) {
844 if (empty($sum_array[$item->id
])) {
845 $sum_array[$item->id
] = 0;
848 $groupsql = $this->groupsql
;
849 $groupwheresql = $this->groupwheresql
;
854 // MDL-10875 Empty grades must be evaluated as grademin, NOT always 0
855 // This query returns a count of ungraded grades (NULL finalgrade OR no matching record in grade_grades table)
856 $SQL = "SELECT COUNT(*) AS count FROM {$CFG->prefix}user u
858 (SELECT userid FROM {$CFG->prefix}grade_grades
859 WHERE itemid = $item->id
860 AND finalgrade IS NOT NULL
863 SELECT DISTINCT(u.id)
864 FROM {$CFG->prefix}user u LEFT JOIN
865 {$CFG->prefix}role_assignments ra ON u.id = ra.userid
867 WHERE ra.roleid in ($this->gradebookroles)
868 AND ra.contextid ".get_related_contexts_string($this->context
)."
872 $ungraded_count = get_field_sql($SQL);
874 if ($meanselection == GRADE_REPORT_MEAN_GRADED
) {
875 $mean_count = $totalcount - $ungraded_count;
876 } else { // Bump up the sum by the number of ungraded items * grademin
877 if (isset($sum_array[$item->id
])) {
878 $sum_array[$item->id
] +
= $ungraded_count * $item->grademin
;
880 $mean_count = $totalcount;
883 $decimalpoints = $this->get_pref('decimalpoints', $item->id
);
884 // Determine which display type to use for this average
885 $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id
);
886 if ($USER->gradeediting
[$this->courseid
]) {
887 $displaytype = GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL
;
888 } elseif ($averagesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT
) { // Inherit specific column or general preference
889 $displaytype = $gradedisplaytype;
890 } else { // General preference overrides specific column display type
891 $displaytype = $averagesdisplaytype;
894 if ($averagesdecimalpoints != GRADE_REPORT_PREFERENCE_INHERIT
) {
895 $decimalpoints = $averagesdecimalpoints;
898 if (!isset($sum_array[$item->id
]) ||
$mean_count == 0) {
899 $avghtml .= '<td class="cell c' . $columncount++
.'">-</td>';
901 $sum = $sum_array[$item->id
];
903 if ($item->scaleid
) {
905 $finalsum = $sum_array[$item->id
];
906 $finalavg = $finalsum/$mean_count;
908 $finalavg = $sum/$mean_count;
910 $scaleval = round($finalavg);
911 $scale_object = new grade_scale(array('id' => $item->scaleid
), false);
912 $gradehtml = $scale_object->get_nearest_item($scaleval);
913 $rawvalue = $scaleval;
915 $gradeval = format_float($sum/$mean_count, $decimalpoints);
916 $gradehtml = $gradeval;
917 $rawvalue = $gradeval;
920 if ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE
) {
921 $gradeval = grade_to_percentage($rawvalue, $item->grademin
, $item->grademax
);
922 $gradehtml = number_format(format_float($gradeval, $decimalpoints), $decimalpoints) . '%';
923 } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER
) {
924 $letters = grade_report
::get_grade_letters();
925 $gradehtml = grade_grade
::get_letter($letters, $gradeval, $item->grademin
, $item->grademax
);
928 $numberofgrades = '';
930 if ($shownumberofgrades) {
931 $numberofgrades = " ($mean_count)";
934 $avghtml .= '<td class="cell c' . $columncount++
.'">'.$gradehtml.$numberofgrades.'</td>';
943 * Builds and return the HTML row of ranges for each column (i.e. range).
944 * @return string HTML
946 function get_rangehtml() {
950 if ($this->get_pref('showranges')) {
951 $rangesdisplaytype = $this->get_pref('rangesdisplaytype');
952 $rangesdecimalpoints = $this->get_pref('rangesdecimalpoints');
953 $scalehtml = '<tr class="r'.$this->rowcount++
.'">'
954 . '<th class="header c0 range" scope="row">'.$this->get_lang_string('range','grades').'</th>';
957 foreach ($this->items
as $item) {
959 $decimalpoints = $this->get_pref('decimalpoints', $item->id
);
960 // Determine which display type to use for this range
961 $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id
);
963 if ($USER->gradeediting
[$this->courseid
]) {
964 $displaytype = GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL
;
965 } elseif ($rangesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT
) { // Inherit specific column or general preference
966 $displaytype = $gradedisplaytype;
967 } else { // General preference overrides specific column display type
968 $displaytype = $rangesdisplaytype;
971 if ($rangesdecimalpoints != GRADE_REPORT_PREFERENCE_INHERIT
) {
972 $decimalpoints = $rangesdecimalpoints;
975 if ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL
) {
976 $grademin = format_float($item->grademin
, $decimalpoints);
977 $grademax = format_float($item->grademax
, $decimalpoints);
978 } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE
) {
981 } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER
) {
982 $letters = grade_report
::get_grade_letters();
983 $grademin = end($letters);
984 $grademax = reset($letters);
987 $scalehtml .= '<th class="header c'.$columncount++
.' range">'. $grademin.'-'. $grademax.'</th>';
989 $scalehtml .= '</tr>';
995 * Given a grade_category, grade_item or grade_grade, this function
996 * figures out the state of the object and builds then returns a div
997 * with the icons needed for the grader report.
999 * @param object $object
1000 * @return string HTML
1002 function get_icons($element) {
1005 if (!$USER->gradeediting
[$this->courseid
]) {
1006 return '<div class="grade_icons" />';
1010 $edit_icon = $this->gtree
->get_edit_icon($element, $this->gpr
);
1011 $edit_calculation_icon = '';
1012 $show_hide_icon = '';
1013 $lock_unlock_icon = '';
1015 if ($this->get_pref('showcalculations')) {
1016 $edit_calculation_icon = $this->gtree
->get_calculation_icon($element, $this->gpr
);
1019 if ($this->get_pref('showeyecons')) {
1020 $show_hide_icon = $this->gtree
->get_hiding_icon($element, $this->gpr
);
1023 if ($this->get_pref('showlocks')) {
1024 $lock_unlock_icon = $this->gtree
->get_locking_icon($element, $this->gpr
);
1027 return '<div class="grade_icons">'.$edit_icon.$edit_calculation_icon.$show_hide_icon.$lock_unlock_icon.'</div>';
1031 * Given a category element returns collapsing +/- icon if available
1032 * @param object $object
1033 * @return string HTML
1035 function get_collapsing_icon($element) {
1038 $contract_expand_icon = '';
1039 // If object is a category, display expand/contract icon
1040 if ($element['type'] == 'category') {
1041 // Load language strings
1042 $strswitch_minus = $this->get_lang_string('aggregatesonly', 'grades');
1043 $strswitch_plus = $this->get_lang_string('gradesonly', 'grades');
1044 $strswitch_whole = $this->get_lang_string('fullmode', 'grades');
1046 $expand_contract = 'switch_minus'; // Default: expanded
1047 // $this->get_pref('aggregationview', $element['object']->id) == GRADE_REPORT_AGGREGATION_VIEW_COMPACT
1049 if (in_array($element['object']->id
, $this->collapsed
['aggregatesonly'])) {
1050 $expand_contract = 'switch_plus';
1051 } elseif (in_array($element['object']->id
, $this->collapsed
['gradesonly'])) {
1052 $expand_contract = 'switch_whole';
1054 $url = $this->gpr
->get_return_url(null, array('target'=>$element['eid'], 'action'=>$expand_contract, 'sesskey'=>sesskey()));
1055 $contract_expand_icon = '<a href="'.$url.'"><img src="'.$CFG->pixpath
.'/t/'.$expand_contract.'.gif" class="iconsmall" alt="'
1056 .$
{'str'.$expand_contract}.'" title="'.$
{'str'.$expand_contract}.'" /></a>';
1058 return $contract_expand_icon;
1062 * Processes a single action against a category, grade_item or grade.
1063 * @param string $target eid ({type}{id}, e.g. c4 for category4)
1064 * @param string $action Which action to take (edit, delete etc...)
1067 function process_action($target, $action) {
1068 // TODO: this code should be in some grade_tree static method
1069 $targettype = substr($target, 0, 1);
1070 $targetid = substr($target, 1);
1073 if ($collapsed = get_user_preferences('grade_report_grader_collapsed_categories')) {
1074 $collapsed = unserialize($collapsed);
1076 $collapsed = array('aggregatesonly' => array(), 'gradesonly' => array());
1080 case 'switch_minus': // Add category to array of aggregatesonly
1081 if (!in_array($targetid, $collapsed['aggregatesonly'])) {
1082 $collapsed['aggregatesonly'][] = $targetid;
1083 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));
1087 case 'switch_plus': // Remove category from array of aggregatesonly, and add it to array of gradesonly
1088 $key = array_search($targetid, $collapsed['aggregatesonly']);
1089 if ($key !== false) {
1090 unset($collapsed['aggregatesonly'][$key]);
1092 if (!in_array($targetid, $collapsed['gradesonly'])) {
1093 $collapsed['gradesonly'][] = $targetid;
1095 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));
1097 case 'switch_whole': // Remove the category from the array of collapsed cats
1098 $key = array_search($targetid, $collapsed['gradesonly']);
1099 if ($key !== false) {
1100 unset($collapsed['gradesonly'][$key]);
1101 set_user_preference('grade_report_grader_collapsed_categories', serialize($collapsed));