4ba9941c |
1 | <?php // $Id$ |
2 | /** |
3 | * File in which the grader_report class is defined. |
4 | * @package gradebook |
5 | */ |
6 | |
eea6690a |
7 | require_once($CFG->dirroot . '/grade/report/lib.php'); |
4ba9941c |
8 | require_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 |
15 | class 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 | |
4ba9941c |
54 | /** |
55 | * Constructor. Sets local copies of user preferences and initialises grade_tree. |
56 | * @param int $courseid |
d30c4481 |
57 | * @param object $gpr grade plugin return tracking object |
eea6690a |
58 | * @param string $context |
59 | * @param int $page The current page being viewed (when report is paged) |
60 | * @param int $sortitemid The id of the grade_item by which to sort the table |
4ba9941c |
61 | */ |
d30c4481 |
62 | function grade_report_grader($courseid, $gpr, $context, $page=null, $sortitemid=null) { |
4ba9941c |
63 | global $CFG; |
d30c4481 |
64 | parent::grade_report($courseid, $gpr, $context, $page); |
4ba9941c |
65 | |
4ba9941c |
66 | $this->sortitemid = $sortitemid; |
67 | |
4ba9941c |
68 | // base url for sorting by first/last name |
65dd61bd |
69 | $this->baseurl = 'index.php?id='.$this->courseid.'&perpage='.$this->get_pref('studentsperpage') |
70 | .'&page='.$this->page; |
4ba9941c |
71 | // |
65dd61bd |
72 | $this->pbarurl = 'index.php?id='.$this->courseid.'&perpage='.$this->get_pref('studentsperpage'); |
4ba9941c |
73 | |
90d3960c |
74 | // Setup groups if requested |
936f1350 |
75 | if ($this->get_pref('showgroups')) { |
4ba9941c |
76 | $this->setup_groups(); |
77 | } |
78 | |
79 | $this->setup_sortitemid(); |
80 | } |
81 | |
4ba9941c |
82 | /** |
83 | * Processes the data sent by the form (grades and feedbacks). |
84 | * @var array $data |
85 | * @return bool Success or Failure (array of errors). |
86 | */ |
87 | function process_data($data) { |
2cc773f5 |
88 | |
89 | if (!has_capability('moodle/grade:override', $this->context)) { |
90 | return false; |
91 | } |
92 | |
4ba9941c |
93 | // always initialize all arrays |
94 | $queue = array(); |
4ba9941c |
95 | foreach ($data as $varname => $postedvalue) { |
4ba9941c |
96 | |
97 | $needsupdate = false; |
98 | $note = false; // TODO implement note?? |
99 | |
100 | // skip, not a grade nor feedback |
79eabc2a |
101 | if (strpos($varname, 'grade') === 0) { |
4ba9941c |
102 | $data_type = 'grade'; |
79eabc2a |
103 | } else if (strpos($varname, 'feedback') === 0) { |
4ba9941c |
104 | $data_type = 'feedback'; |
105 | } else { |
106 | continue; |
107 | } |
108 | |
109 | $gradeinfo = explode("_", $varname); |
4ba9941c |
110 | $userid = clean_param($gradeinfo[1], PARAM_INT); |
111 | $itemid = clean_param($gradeinfo[2], PARAM_INT); |
112 | |
29a5680e |
113 | $oldvalue = $data->{'old'.$varname}; |
114 | |
115 | // was change requested? |
116 | if ($oldvalue == $postedvalue) { |
117 | continue; |
118 | } |
119 | |
4ba9941c |
120 | if (!$grade_item = grade_item::fetch(array('id'=>$itemid, 'courseid'=>$this->courseid))) { // we must verify course id here! |
121 | error('Incorrect grade item id'); |
122 | } |
123 | |
124 | // Pre-process grade |
125 | if ($data_type == 'grade') { |
4256a134 |
126 | $feedback = false; |
127 | $feedbackformat = false; |
4ba9941c |
128 | if ($grade_item->gradetype == GRADE_TYPE_SCALE) { |
129 | if ($postedvalue == -1) { // -1 means no grade |
130 | $finalgrade = null; |
131 | } else { |
29a5680e |
132 | $finalgrade = $postedvalue; |
4ba9941c |
133 | } |
134 | } else { |
29a5680e |
135 | $trimmed = trim($postedvalue); |
136 | if (empty($trimmed)) { // empty string means no grade |
4ba9941c |
137 | $finalgrade = null; |
138 | } else { |
eea6690a |
139 | $finalgrade = $this->format_grade($postedvalue); |
4ba9941c |
140 | } |
141 | } |
79eabc2a |
142 | |
143 | } else if ($data_type == 'feedback') { |
4256a134 |
144 | $finalgrade = false; |
79eabc2a |
145 | $trimmed = trim($postedvalue); |
146 | if (empty($trimmed)) { |
29a5680e |
147 | $feedback = NULL; |
e1d2692a |
148 | } else { |
29a5680e |
149 | $feedback = stripslashes($postedvalue); |
e1d2692a |
150 | } |
4ba9941c |
151 | } |
4ba9941c |
152 | |
29a5680e |
153 | $grade_item->update_final_grade($userid, $finalgrade, 'gradebook', $note, $feedback); |
4ba9941c |
154 | } |
155 | |
156 | return true; |
157 | } |
158 | |
4ba9941c |
159 | |
160 | /** |
161 | * Setting the sort order, this depends on last state |
162 | * all this should be in the new table class that we might need to use |
163 | * for displaying grades. |
164 | */ |
165 | function setup_sortitemid() { |
63d6efa2 |
166 | |
167 | global $SESSION; |
168 | |
4ba9941c |
169 | if ($this->sortitemid) { |
170 | if (!isset($SESSION->gradeuserreport->sort)) { |
171 | $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; |
172 | } else { |
173 | // this is the first sort, i.e. by last name |
174 | if (!isset($SESSION->gradeuserreport->sortitemid)) { |
175 | $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; |
176 | } else if ($SESSION->gradeuserreport->sortitemid == $this->sortitemid) { |
177 | // same as last sort |
178 | if ($SESSION->gradeuserreport->sort == 'ASC') { |
179 | $this->sortorder = $SESSION->gradeuserreport->sort = 'DESC'; |
180 | } else { |
181 | $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; |
182 | } |
183 | } else { |
184 | $this->sortorder = $SESSION->gradeuserreport->sort = 'ASC'; |
185 | } |
186 | } |
187 | $SESSION->gradeuserreport->sortitemid = $this->sortitemid; |
188 | } else { |
189 | // not requesting sort, use last setting (for paging) |
190 | |
191 | if (isset($SESSION->gradeuserreport->sortitemid)) { |
192 | $this->sortitemid = $SESSION->gradeuserreport->sortitemid; |
193 | } |
194 | if (isset($SESSION->gradeuserreport->sort)) { |
195 | $this->sortorder = $SESSION->gradeuserreport->sort; |
196 | } else { |
197 | $this->sortorder = 'ASC'; |
198 | } |
199 | } |
200 | } |
201 | |
4ba9941c |
202 | /** |
203 | * pulls out the userids of the users to be display, and sort them |
204 | * the right outer join is needed because potentially, it is possible not |
205 | * to have the corresponding entry in grade_grades table for some users |
206 | * this is check for user roles because there could be some users with grades |
207 | * but not supposed to be displayed |
208 | */ |
209 | function load_users() { |
210 | global $CFG; |
211 | |
212 | if (is_numeric($this->sortitemid)) { |
213 | $sql = "SELECT u.id, u.firstname, u.lastname |
214 | FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN |
215 | {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid) |
216 | LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid |
217 | $this->groupsql |
218 | WHERE ra.roleid in ($this->gradebookroles) |
219 | $this->groupwheresql |
220 | AND ra.contextid ".get_related_contexts_string($this->context)." |
221 | ORDER BY g.finalgrade $this->sortorder"; |
936f1350 |
222 | $this->users = get_records_sql($sql, $this->get_pref('studentsperpage') * $this->page, |
223 | $this->get_pref('studentsperpage')); |
4ba9941c |
224 | } else { |
225 | // default sort |
226 | // get users sorted by lastname |
227 | $this->users = get_role_users(@implode(',', $CFG->gradebookroles), $this->context, false, |
228 | 'u.id, u.firstname, u.lastname', 'u.'.$this->sortitemid .' '. $this->sortorder, |
936f1350 |
229 | false, $this->page * $this->get_pref('studentsperpage'), $this->get_pref('studentsperpage'), |
e5161d0c |
230 | $this->currentgroup); |
4ba9941c |
231 | // need to cut users down by groups |
232 | |
233 | } |
234 | |
235 | if (empty($this->users)) { |
236 | $this->userselect = ''; |
237 | $this->users = array(); |
238 | } else { |
239 | $this->userselect = 'AND g.userid in ('.implode(',', array_keys($this->users)).')'; |
240 | } |
241 | |
242 | return $this->users; |
243 | } |
244 | |
245 | /** |
90d3960c |
246 | * Fetches and returns a count of all the users that will be shown on this page. |
4ba9941c |
247 | * @return int Count of users |
248 | */ |
66b9da27 |
249 | function get_numusers() { |
4ba9941c |
250 | global $CFG; |
66b9da27 |
251 | |
4ba9941c |
252 | $countsql = "SELECT COUNT(DISTINCT u.id) |
253 | FROM {$CFG->prefix}grade_grades g RIGHT OUTER JOIN |
254 | {$CFG->prefix}user u ON (u.id = g.userid AND g.itemid = $this->sortitemid) |
255 | LEFT JOIN {$CFG->prefix}role_assignments ra ON u.id = ra.userid |
66b9da27 |
256 | $this->groupsql |
4ba9941c |
257 | WHERE ra.roleid in ($this->gradebookroles) |
66b9da27 |
258 | $this->groupwheresql |
4ba9941c |
259 | AND ra.contextid ".get_related_contexts_string($this->context); |
260 | return count_records_sql($countsql); |
261 | } |
262 | |
263 | /** |
264 | * we supply the userids in this query, and get all the grades |
265 | * pulls out all the grades, this does not need to worry about paging |
266 | */ |
267 | function load_final_grades() { |
268 | global $CFG; |
269 | |
23207a1a |
270 | // please note that we must fetch all grade_grades fields if we want to contruct grade_grade object from it! |
271 | $sql = "SELECT g.*, gt.feedback, gt.feedbackformat, gi.grademin, gi.grademax |
272 | FROM {$CFG->prefix}grade_items gi, |
273 | {$CFG->prefix}grade_grades g |
274 | LEFT JOIN {$CFG->prefix}grade_grades_text gt ON g.id = gt.gradeid |
275 | WHERE g.itemid = gi.id AND gi.courseid = $this->courseid $this->userselect"; |
4ba9941c |
276 | |
277 | if ($grades = get_records_sql($sql)) { |
278 | foreach ($grades as $grade) { |
279 | $this->finalgrades[$grade->userid][$grade->itemid] = $grade; |
280 | } |
281 | } |
282 | } |
283 | |
284 | /** |
285 | * Builds and returns a div with on/off toggles. |
286 | * @return string HTML code |
287 | */ |
288 | function get_toggles_html() { |
289 | global $USER; |
290 | $html = '<div id="grade-report-toggles">'; |
2cc773f5 |
291 | if ($USER->gradeediting[$this->courseid]) { |
292 | if (has_capability('moodle/grade:manage', $this->context) or has_capability('moodle/grade:hide', $this->context)) { |
293 | $html .= $this->print_toggle('eyecons', true); |
294 | } |
295 | if (has_capability('moodle/grade:manage', $this->context) |
296 | or has_capability('moodle/grade:lock', $this->context) |
297 | or has_capability('moodle/grade:unlock', $this->context)) { |
298 | $html .= $this->print_toggle('locks', true); |
299 | } |
300 | if (has_capability('moodle/grade:manage', $this->context)) { |
301 | $html .= $this->print_toggle('calculations', true); |
302 | } |
4ba9941c |
303 | } |
304 | |
36e53792 |
305 | $html .= $this->print_toggle('averages', true); |
4ba9941c |
306 | $html .= $this->print_toggle('groups', true); |
61649211 |
307 | $html .= $this->print_toggle('ranges', true); |
4ba9941c |
308 | $html .= '</div>'; |
309 | return $html; |
310 | } |
311 | |
312 | /** |
313 | * Shortcut function for printing the grader report toggles. |
314 | * @param string $type The type of toggle |
315 | * @param bool $return Whether to return the HTML string rather than printing it |
316 | * @return void |
317 | */ |
318 | function print_toggle($type, $return=false) { |
319 | global $CFG; |
320 | |
321 | $icons = array('eyecons' => 'hide', |
322 | 'calculations' => 'calc', |
323 | 'locks' => 'lock', |
36e53792 |
324 | 'averages' => 'sigma'); |
4ba9941c |
325 | |
326 | $pref_name = 'grade_report_show' . $type; |
eea6690a |
327 | $show_pref = get_user_preferences($pref_name, $CFG->$pref_name); |
4ba9941c |
328 | |
388234f4 |
329 | $strshow = $this->get_lang_string('show' . $type, 'grades'); |
330 | $strhide = $this->get_lang_string('hide' . $type, 'grades'); |
4ba9941c |
331 | |
332 | $show_hide = 'show'; |
333 | $toggle_action = 1; |
334 | |
335 | if ($show_pref) { |
336 | $show_hide = 'hide'; |
337 | $toggle_action = 0; |
338 | } |
339 | |
340 | if (array_key_exists($type, $icons)) { |
341 | $image_name = $icons[$type]; |
342 | } else { |
343 | $image_name = $type; |
344 | } |
345 | |
346 | $string = ${'str' . $show_hide}; |
347 | |
348 | $img = '<img src="'.$CFG->pixpath.'/t/'.$image_name.'.gif" class="iconsmall" alt="' |
349 | .$string.'" title="'.$string.'" />'. "\n"; |
350 | |
351 | $retval = '<div class="gradertoggle">' . $img . '<a href="' . $this->baseurl . "&toggle=$toggle_action&toggle_type=$type\">" |
352 | . $string . '</a></div>'; |
353 | |
354 | if ($return) { |
355 | return $retval; |
356 | } else { |
357 | echo $retval; |
358 | } |
359 | } |
360 | |
361 | /** |
362 | * Builds and returns the HTML code for the headers. |
363 | * @return string $headerhtml |
364 | */ |
d30c4481 |
365 | function get_headerhtml() { |
4ba9941c |
366 | global $CFG, $USER; |
367 | |
48b5d8f3 |
368 | $strsortasc = $this->get_lang_string('sortasc', 'grades'); |
369 | $strsortdesc = $this->get_lang_string('sortdesc', 'grades'); |
370 | $strfirstname = $this->get_lang_string('firstname'); |
371 | $strlastname = $this->get_lang_string('lastname'); |
372 | |
4ba9941c |
373 | if ($this->sortitemid === 'lastname') { |
374 | if ($this->sortorder == 'ASC') { |
375 | $lastarrow = print_arrow('up', $strsortasc, true); |
376 | } else { |
377 | $lastarrow = print_arrow('down', $strsortdesc, true); |
378 | } |
379 | } else { |
380 | $lastarrow = ''; |
381 | } |
382 | |
383 | if ($this->sortitemid === 'firstname') { |
384 | if ($this->sortorder == 'ASC') { |
385 | $firstarrow = print_arrow('up', $strsortasc, true); |
386 | } else { |
387 | $firstarrow = print_arrow('down', $strsortdesc, true); |
388 | } |
389 | } else { |
390 | $firstarrow = ''; |
391 | } |
392 | // Prepare Table Headers |
393 | $headerhtml = ''; |
394 | |
395 | $numrows = count($this->gtree->levels); |
396 | |
cb7fe7b4 |
397 | $columns_to_unset = array(); |
398 | |
4ba9941c |
399 | foreach ($this->gtree->levels as $key=>$row) { |
400 | if ($key == 0) { |
cb7fe7b4 |
401 | // do not display course grade category |
4ba9941c |
402 | // continue; |
403 | } |
404 | |
405 | $headerhtml .= '<tr class="heading">'; |
406 | |
407 | if ($key == $numrows - 1) { |
48b5d8f3 |
408 | $headerhtml .= '<th class="user"><a href="'.$this->baseurl.'&sortitemid=firstname">' . $strfirstname . '</a> ' //TODO: localize |
409 | . $firstarrow. '/ <a href="'.$this->baseurl.'&sortitemid=lastname">' . $strlastname . '</a>'. $lastarrow .'</th>'; |
4ba9941c |
410 | } else { |
411 | $headerhtml .= '<td class="topleft"> </td>'; |
412 | } |
413 | |
cb7fe7b4 |
414 | foreach ($row as $columnkey => $element) { |
415 | $eid = $element['eid']; |
416 | $object = $element['object']; |
417 | $type = $element['type']; |
438a5aa9 |
418 | $categorystate = @$element['categorystate']; |
8c5a416e |
419 | |
4ba9941c |
420 | if (!empty($element['colspan'])) { |
421 | $colspan = 'colspan="'.$element['colspan'].'"'; |
422 | } else { |
423 | $colspan = ''; |
424 | } |
425 | |
426 | if (!empty($element['depth'])) { |
427 | $catlevel = ' catlevel'.$element['depth']; |
428 | } else { |
429 | $catlevel = ''; |
430 | } |
431 | |
cb7fe7b4 |
432 | // Element is a filler |
4ba9941c |
433 | if ($type == 'filler' or $type == 'fillerfirst' or $type == 'fillerlast') { |
63d6efa2 |
434 | $headerhtml .= '<th class="'.$type.$catlevel.'" '.$colspan.'> </th>'; |
cb7fe7b4 |
435 | } |
436 | // Element is a category |
437 | else if ($type == 'category' && !in_array($eid, $columns_to_unset)) { |
63d6efa2 |
438 | $headerhtml .= '<th class="category'.$catlevel.'" '.$colspan.'>'.$element['object']->get_name(); |
4ba9941c |
439 | |
440 | // Print icons |
2cc773f5 |
441 | if ($USER->gradeediting[$this->courseid]) { |
d30c4481 |
442 | $headerhtml .= $this->get_icons($element); |
4ba9941c |
443 | } |
444 | |
63d6efa2 |
445 | $headerhtml .= '</th>'; |
cb7fe7b4 |
446 | } |
447 | // Element is a grade_item |
448 | elseif (!in_array($eid, $columns_to_unset)) { |
4ba9941c |
449 | if ($element['object']->id == $this->sortitemid) { |
450 | if ($this->sortorder == 'ASC') { |
451 | $arrow = print_arrow('up', $strsortasc, true); |
452 | } else { |
453 | $arrow = print_arrow('down', $strsortdesc, true); |
454 | } |
455 | } else { |
456 | $arrow = ''; |
457 | } |
458 | |
459 | $dimmed = ''; |
460 | if ($element['object']->is_hidden()) { |
461 | $dimmed = ' dimmed_text '; |
462 | } |
463 | |
464 | if ($object->itemtype == 'mod') { |
465 | $icon = '<img src="'.$CFG->modpixpath.'/'.$object->itemmodule.'/icon.gif" class="icon" alt="' |
388234f4 |
466 | .$this->get_lang_string('modulename', $object->itemmodule).'"/>'; |
4ba9941c |
467 | } else if ($object->itemtype == 'manual') { |
468 | //TODO: add manual grading icon |
388234f4 |
469 | $icon = '<img src="'.$CFG->pixpath.'/t/edit.gif" class="icon" alt="'.$this->get_lang_string('manualgrade', 'grades') |
4ba9941c |
470 | .'"/>'; |
471 | } |
472 | |
473 | |
474 | $headerhtml .= '<th class="'.$type.$catlevel.$dimmed.'"><a href="'.$this->baseurl.'&sortitemid=' |
475 | . $element['object']->id .'">'. $element['object']->get_name() |
476 | . '</a>' . $arrow; |
477 | |
d30c4481 |
478 | $headerhtml .= $this->get_icons($element) . '</th>'; |
4ba9941c |
479 | |
480 | $this->items[$element['object']->sortorder] =& $element['object']; |
481 | } |
482 | |
483 | } |
484 | |
485 | $headerhtml .= '</tr>'; |
486 | } |
487 | return $headerhtml; |
488 | } |
489 | |
490 | /** |
491 | * Builds and return the HTML rows of the table (grades headed by student). |
492 | * @return string HTML |
493 | */ |
d30c4481 |
494 | function get_studentshtml() { |
4ba9941c |
495 | global $CFG, $USER; |
496 | $studentshtml = ''; |
388234f4 |
497 | $strfeedback = $this->get_lang_string("feedback"); |
18effef4 |
498 | $gradetabindex = 1; |
e7536c92 |
499 | $showuserimage = $this->get_pref('showuserimage'); |
c0c1e7c2 |
500 | $numusers = count($this->users); |
4ba9941c |
501 | |
388234f4 |
502 | // Preload scale objects for items with a scaleid |
503 | $scales_list = ''; |
c0c1e7c2 |
504 | $tabindices = array(); |
388234f4 |
505 | foreach ($this->items as $item) { |
506 | if (!empty($item->scaleid)) { |
507 | $scales_list .= "$item->scaleid,"; |
508 | } |
c0c1e7c2 |
509 | $tabindices[$item->id]['grade'] = $gradetabindex; |
510 | $tabindices[$item->id]['feedback'] = $gradetabindex + $numusers; |
511 | $gradetabindex += $numusers * 2; |
388234f4 |
512 | } |
513 | $scales_array = array(); |
514 | |
515 | if (!empty($scales_list)) { |
516 | $scales_list = substr($scales_list, 0, -1); |
517 | $scales_array = get_records_list('scale', 'id', $scales_list); |
518 | } |
519 | |
4ba9941c |
520 | foreach ($this->users as $userid => $user) { |
521 | // Student name and link |
e7536c92 |
522 | $user_pic = null; |
523 | if ($showuserimage) { |
524 | $user_pic = '<div class="userpic">' . print_user_picture($user->id, $this->courseid, true, 0, true) . '</div>'; |
525 | } |
526 | |
527 | $studentshtml .= '<tr><th class="user">' . $user_pic . '<a href="' . $CFG->wwwroot . '/user/view.php?id=' |
4ba9941c |
528 | . $user->id . '">' . fullname($user) . '</a></th>'; |
e7536c92 |
529 | |
2cc773f5 |
530 | foreach ($this->items as $itemid=>$item) { |
e50ce569 |
531 | // Get the decimal points preference for this item |
532 | $decimalpoints = $this->get_pref('decimalpoints', $item->id); |
4ba9941c |
533 | |
534 | if (isset($this->finalgrades[$userid][$item->id])) { |
535 | $gradeval = $this->finalgrades[$userid][$item->id]->finalgrade; |
bb384a8e |
536 | |
3ee5c201 |
537 | $grade = new grade_grade($this->finalgrades[$userid][$item->id], false); |
bb384a8e |
538 | $grade->feedback = stripslashes_safe($this->finalgrades[$userid][$item->id]->feedback); |
bd6c9ddb |
539 | $grade->feedbackformat = $this->finalgrades[$userid][$item->id]->feedbackformat; |
4ba9941c |
540 | |
541 | } else { |
542 | $gradeval = null; |
2cc773f5 |
543 | $grade = new grade_grade(array('userid'=>$userid, 'itemid'=>$item->id), false); |
4ba9941c |
544 | $grade->feedback = ''; |
545 | } |
546 | |
2cc773f5 |
547 | $grade->courseid = $this->courseid; |
548 | $grade->grade_item =& $this->items[$itemid]; // this speedsup is_hidden() and other grade_grade methods |
549 | |
550 | // emulate grade element |
551 | $element = array('eid'=>'g'.$grade->id, 'object'=>$grade, 'type'=>'grade'); |
552 | |
4ba9941c |
553 | if ($grade->is_overridden()) { |
554 | $studentshtml .= '<td class="overridden">'; |
555 | } else { |
556 | $studentshtml .= '<td>'; |
557 | } |
558 | |
23207a1a |
559 | if ($grade->is_excluded()) { |
560 | $studentshtml .= get_string('excluded', 'grades'); // TODO: improve visual representation of excluded grades |
561 | } |
562 | |
4ba9941c |
563 | // Do not show any icons if no grade (no record in DB to match) |
79eabc2a |
564 | // TODO: change edit/hide/etc. links to use itemid and userid to allow creating of new grade objects |
2cc773f5 |
565 | if (!$item->needsupdate and !empty($grade->id) and $USER->gradeediting[$this->courseid]) { |
566 | $studentshtml .= $this->get_icons($element); |
4ba9941c |
567 | } |
568 | |
569 | // if in editting mode, we need to print either a text box |
570 | // or a drop down (for scales) |
4ba9941c |
571 | // grades in item of type grade category or course are not directly editable |
d14ae855 |
572 | if ($item->needsupdate) { |
573 | $studentshtml .= '<span class="gradingerror">'.get_string('error').'</span>'; |
574 | |
2cc773f5 |
575 | } else if ($USER->gradeediting[$this->courseid]) { |
4ba9941c |
576 | // We need to retrieve each grade_grade object from DB in order to |
577 | // know if they are hidden/locked |
578 | |
388234f4 |
579 | if ($item->scaleid && !empty($scales_array[$item->scaleid])) { |
580 | $scale = $scales_array[$item->scaleid]; |
581 | |
582 | $scales = explode(",", $scale->scale); |
583 | // reindex because scale is off 1 |
584 | $i = 0; |
585 | foreach ($scales as $scaleoption) { |
586 | $i++; |
587 | $scaleopt[$i] = $scaleoption; |
588 | } |
589 | |
590 | if ($this->get_pref('quickgrading') and $grade->is_editable()) { |
29a5680e |
591 | $studentshtml .= '<input type="hidden" name="oldgrade_'.$userid.'_' |
592 | .$item->id.'" value="'.$gradeval.'"/>'; |
388234f4 |
593 | $studentshtml .= choose_from_menu($scaleopt, 'grade_'.$userid.'_'.$item->id, |
c0c1e7c2 |
594 | $gradeval, $this->get_lang_string('nograde'), '', '-1', |
595 | true, false, $tabindices[$item->id]['grade']); |
388234f4 |
596 | } elseif(!empty($scale)) { |
4ba9941c |
597 | $scales = explode(",", $scale->scale); |
4ba9941c |
598 | |
388234f4 |
599 | // invalid grade if gradeval < 1 |
600 | if ((int) $gradeval < 1) { |
601 | $studentshtml .= '-'; |
4ba9941c |
602 | } else { |
388234f4 |
603 | $studentshtml .= $scales[$gradeval-1]; |
4ba9941c |
604 | } |
388234f4 |
605 | } else { |
606 | // no such scale, throw error? |
4ba9941c |
607 | } |
79eabc2a |
608 | |
bb384a8e |
609 | } else if ($item->gradetype != GRADE_TYPE_TEXT) { // Value type |
936f1350 |
610 | if ($this->get_pref('quickgrading') and $grade->is_editable()) { |
29a5680e |
611 | $value = $this->get_grade_clean($gradeval, $decimalpoints); |
612 | $studentshtml .= '<input type="hidden" name="oldgrade_'.$userid.'_'.$item->id.'" value="'.$value.'" />'; |
c0c1e7c2 |
613 | $studentshtml .= '<input size="6" tabindex="' . $tabindices[$item->id]['grade'] . '" type="text" name="grade_' |
614 | .$userid.'_' .$item->id.'" value="'.$value.'" />'; |
4ba9941c |
615 | } else { |
e50ce569 |
616 | $studentshtml .= $this->get_grade_clean($gradeval, $decimalpoints); |
4ba9941c |
617 | } |
618 | } |
619 | |
620 | |
621 | // If quickfeedback is on, print an input element |
936f1350 |
622 | if ($this->get_pref('quickfeedback') and $grade->is_editable()) { |
623 | if ($this->get_pref('quickgrading')) { |
4ba9941c |
624 | $studentshtml .= '<br />'; |
625 | } |
29a5680e |
626 | $studentshtml .= '<input type="hidden" name="oldfeedback_' |
627 | .$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />'; |
c0c1e7c2 |
628 | $studentshtml .= '<input tabindex="' . $tabindices[$item->id]['feedback'] . '" size="6" type="text" name="feedback_' |
29a5680e |
629 | .$userid.'_'.$item->id.'" value="' . s($grade->feedback) . '" />'; |
4ba9941c |
630 | } |
631 | |
4ba9941c |
632 | } else { |
2f61fc0e |
633 | // Percentage format if specified by user (check each item for a set preference) |
634 | $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id); |
e50ce569 |
635 | |
2f61fc0e |
636 | $percentsign = ''; |
9580a21f |
637 | $grademin = $item->grademin; |
638 | $grademax = $item->grademax; |
2f61fc0e |
639 | |
640 | if ($gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE) { |
32b97bb2 |
641 | if (!is_null($gradeval)) { |
3ee5c201 |
642 | $gradeval = grade_grade::standardise_score($gradeval, $grademin, $grademax, 0, 100); |
32b97bb2 |
643 | } |
2f61fc0e |
644 | $percentsign = '%'; |
645 | } |
646 | |
4ba9941c |
647 | // If feedback present, surround grade with feedback tooltip |
648 | if (!empty($grade->feedback)) { |
bd6c9ddb |
649 | if ($grade->feedbackformat == 1) { |
bd6c9ddb |
650 | $overlib = "return overlib('" . s(ltrim($grade->feedback)) . "', FULLHTML);"; |
bb384a8e |
651 | } else { |
652 | $overlib = "return overlib('" . ($grade->feedback) . "', CAPTION, '$strfeedback');"; |
bd6c9ddb |
653 | } |
654 | |
655 | $studentshtml .= '<span onmouseover="' . $overlib . '" onmouseout="return nd();">'; |
4ba9941c |
656 | } |
657 | |
d14ae855 |
658 | if ($item->needsupdate) { |
659 | $studentshtml .= '<span class="gradingerror">'.get_string('error').'</span>'; |
660 | |
661 | } else if ($gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER) { |
32b97bb2 |
662 | $letters = grade_report::get_grade_letters(); |
663 | if (!is_null($gradeval)) { |
3ee5c201 |
664 | $studentshtml .= grade_grade::get_letter($letters, $gradeval, $grademin, $grademax); |
32b97bb2 |
665 | } |
666 | } else if ($item->scaleid && !empty($scales_array[$item->scaleid]) |
667 | && $gradedisplaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL) { |
388234f4 |
668 | $scale = $scales_array[$item->scaleid]; |
669 | $scales = explode(",", $scale->scale); |
4ba9941c |
670 | |
388234f4 |
671 | // invalid grade if gradeval < 1 |
672 | if ((int) $gradeval < 1) { |
673 | $studentshtml .= '-'; |
4ba9941c |
674 | } else { |
388234f4 |
675 | $studentshtml .= $scales[$gradeval-1]; |
4ba9941c |
676 | } |
677 | } else { |
678 | if (is_null($gradeval)) { |
679 | $studentshtml .= '-'; |
680 | } else { |
e50ce569 |
681 | $studentshtml .= $this->get_grade_clean($gradeval, $decimalpoints). $percentsign; |
4ba9941c |
682 | } |
683 | } |
684 | if (!empty($grade->feedback)) { |
685 | $studentshtml .= '</span>'; |
686 | } |
687 | } |
688 | |
689 | if (!empty($this->gradeserror[$item->id][$userid])) { |
690 | $studentshtml .= $this->gradeserror[$item->id][$userid]; |
691 | } |
692 | |
693 | $studentshtml .= '</td>' . "\n"; |
694 | } |
695 | $studentshtml .= '</tr>'; |
696 | } |
697 | return $studentshtml; |
698 | } |
699 | |
700 | /** |
5b508a08 |
701 | * Builds and return the HTML row of column totals. |
702 | * @param bool $grouponly Whether to return only group averages or all averages. |
4ba9941c |
703 | * @return string HTML |
704 | */ |
5b508a08 |
705 | function get_avghtml($grouponly=false) { |
2f61fc0e |
706 | global $CFG, $USER; |
4ba9941c |
707 | |
5b508a08 |
708 | $averagesdisplaytype = $this->get_pref('averagesdisplaytype'); |
709 | $averagesdecimalpoints = $this->get_pref('averagesdecimalpoints'); |
710 | $meanselection = $this->get_pref('meanselection'); |
711 | $avghtml = ''; |
3446013d |
712 | |
5b508a08 |
713 | if ($grouponly) { |
714 | $straverage = get_string('groupavg', 'grades'); |
715 | $showaverages = $this->currentgroup && $this->get_pref('showgroups'); |
716 | $groupsql = $this->groupsql; |
717 | $groupwheresql = $this->groupwheresql; |
3446013d |
718 | } else { |
5b508a08 |
719 | $straverage = get_string('average', 'grades'); |
720 | $showaverages = $this->get_pref('showaverages'); |
721 | $groupsql = null; |
722 | $groupwheresql = null; |
3446013d |
723 | } |
724 | |
5b508a08 |
725 | if ($meanselection == GRADE_AGGREGATE_MEAN_GRADED) { |
3446013d |
726 | // non empty grades |
727 | $meanstr = "AND NOT g.finalgrade IS NULL"; |
728 | } else { |
729 | $meanstr = ""; |
730 | } |
5b508a08 |
731 | if ($showaverages) { |
4ba9941c |
732 | |
5b508a08 |
733 | /** SQL for finding the SUM grades of all visible users ($CFG->gradebookroles) or group sum*/ |
5e623a33 |
734 | // do not sum -1 (no grade), treat as 0 for now |
66b9da27 |
735 | $SQL = "SELECT g.itemid, SUM(g.finalgrade) as sum, COUNT(DISTINCT(u.id)) as count |
4ba9941c |
736 | FROM {$CFG->prefix}grade_items gi LEFT JOIN |
737 | {$CFG->prefix}grade_grades g ON gi.id = g.itemid RIGHT OUTER JOIN |
738 | {$CFG->prefix}user u ON u.id = g.userid LEFT JOIN |
739 | {$CFG->prefix}role_assignments ra ON u.id = ra.userid |
5b508a08 |
740 | $groupsql |
4ba9941c |
741 | WHERE gi.courseid = $this->courseid |
5b508a08 |
742 | $groupwheresql |
4ba9941c |
743 | AND ra.roleid in ($this->gradebookroles) |
744 | AND ra.contextid ".get_related_contexts_string($this->context)." |
3446013d |
745 | $meanstr |
4ba9941c |
746 | GROUP BY g.itemid"; |
747 | |
5b508a08 |
748 | $sum_array = array(); |
749 | $count_array = array(); |
4ba9941c |
750 | $sums = get_records_sql($SQL); |
5e623a33 |
751 | |
4ba9941c |
752 | foreach ($sums as $itemid => $csum) { |
5b508a08 |
753 | $sum_array[$itemid] = $csum->sum; |
754 | $count_array[$itemid] = $csum->count; |
4ba9941c |
755 | } |
756 | |
5b508a08 |
757 | $avghtml = '<tr><th>'.$straverage.'</th>'; |
4ba9941c |
758 | foreach ($this->items as $item) { |
e50ce569 |
759 | $decimalpoints = $this->get_pref('decimalpoints', $item->id); |
bb384a8e |
760 | // Determine which display type to use for this average |
761 | $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id); |
2cc773f5 |
762 | if ($USER->gradeediting[$this->courseid]) { |
db4c7968 |
763 | $displaytype = GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL; |
e50ce569 |
764 | } elseif ($averagesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // Inherit specific column or general preference |
bb384a8e |
765 | $displaytype = $gradedisplaytype; |
766 | } else { // General preference overrides specific column display type |
767 | $displaytype = $averagesdisplaytype; |
768 | } |
769 | |
5b508a08 |
770 | if ($averagesdecimalpoints != GRADE_REPORT_PREFERENCE_INHERIT) { |
771 | $decimalpoints = $averagesdecimalpoints; |
772 | } |
773 | |
774 | if (empty($count_array[$item->id]) || !isset($sum_array[$item->id])) { |
775 | $avghtml .= '<td>-</td>'; |
4ba9941c |
776 | } else { |
5b508a08 |
777 | $sum = $sum_array[$item->id]; |
ac04f85d |
778 | |
779 | if ($item->scaleid) { |
5b508a08 |
780 | if ($grouponly) { |
781 | $finalsum = $sum_array[$item->id]; |
782 | $finalavg = $finalsum/$count_array[$item->id]; |
783 | } else { |
784 | $finalavg = $sum/$count_array[$item->id]; |
785 | } |
786 | |
787 | $scaleval = round($this->get_grade_clean($finalavg, $decimalpoints)); |
acdc8e8a |
788 | $scale_object = new grade_scale(array('id' => $item->scaleid), false); |
789 | $gradehtml = $scale_object->get_nearest_item($scaleval); |
2f61fc0e |
790 | $rawvalue = $scaleval; |
3af29899 |
791 | } else { |
5b508a08 |
792 | $gradeval = $this->get_grade_clean($sum/$count_array[$item->id], $decimalpoints); |
bb384a8e |
793 | |
e50ce569 |
794 | $gradehtml = round($gradeval, $decimalpoints); |
2f61fc0e |
795 | $rawvalue = $gradeval; |
796 | } |
bb384a8e |
797 | |
2f61fc0e |
798 | if ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE) { |
3ee5c201 |
799 | $gradeval = grade_grade::standardise_score($rawvalue, $item->grademin, $item->grademax, 0, 100); |
e50ce569 |
800 | $gradehtml = round($gradeval, $decimalpoints) . '%'; |
32b97bb2 |
801 | } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER) { |
802 | $letters = grade_report::get_grade_letters(); |
3ee5c201 |
803 | $gradehtml = grade_grade::get_letter($letters, $gradeval, $item->grademin, $item->grademax); |
3af29899 |
804 | } |
bb384a8e |
805 | |
5b508a08 |
806 | $avghtml .= '<td>'.$gradehtml.'</td>'; |
4ba9941c |
807 | } |
808 | } |
5b508a08 |
809 | $avghtml .= '</tr>'; |
4ba9941c |
810 | } |
5b508a08 |
811 | return $avghtml; |
4ba9941c |
812 | } |
813 | |
e5161d0c |
814 | /** |
2f61fc0e |
815 | * Builds and return the HTML row of ranges for each column (i.e. range). |
e5161d0c |
816 | * @return string HTML |
817 | */ |
2f61fc0e |
818 | function get_rangehtml() { |
819 | global $USER; |
820 | |
4ba9941c |
821 | $scalehtml = ''; |
61649211 |
822 | if ($this->get_pref('showranges')) { |
2f61fc0e |
823 | $rangesdisplaytype = $this->get_pref('rangesdisplaytype'); |
5b508a08 |
824 | $rangesdecimalpoints = $this->get_pref('rangesdecimalpoints'); |
fbb00432 |
825 | $scalehtml = '<tr><th class="range">'.$this->get_lang_string('range','grades').'</th>'; |
5b508a08 |
826 | |
4ba9941c |
827 | foreach ($this->items as $item) { |
e50ce569 |
828 | $decimalpoints = $this->get_pref('decimalpoints', $item->id); |
2f61fc0e |
829 | // Determine which display type to use for this range |
830 | $gradedisplaytype = $this->get_pref('gradedisplaytype', $item->id); |
db4c7968 |
831 | |
2cc773f5 |
832 | if ($USER->gradeediting[$this->courseid]) { |
db4c7968 |
833 | $displaytype = GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL; |
e50ce569 |
834 | } elseif ($rangesdisplaytype == GRADE_REPORT_PREFERENCE_INHERIT) { // Inherit specific column or general preference |
2f61fc0e |
835 | $displaytype = $gradedisplaytype; |
836 | } else { // General preference overrides specific column display type |
837 | $displaytype = $rangesdisplaytype; |
838 | } |
839 | |
5b508a08 |
840 | if ($rangesdecimalpoints != GRADE_REPORT_PREFERENCE_INHERIT) { |
841 | $decimalpoints = $rangesdecimalpoints; |
842 | } |
843 | |
db4c7968 |
844 | if ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_REAL) { |
e50ce569 |
845 | $grademin = $this->get_grade_clean($item->grademin, $decimalpoints); |
846 | $grademax = $this->get_grade_clean($item->grademax, $decimalpoints); |
2f61fc0e |
847 | } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_PERCENTAGE) { |
848 | $grademin = 0; |
849 | $grademax = 100; |
32b97bb2 |
850 | } elseif ($displaytype == GRADE_REPORT_GRADE_DISPLAY_TYPE_LETTER) { |
851 | $letters = grade_report::get_grade_letters(); |
852 | $grademin = end($letters); |
853 | $grademax = reset($letters); |
2f61fc0e |
854 | } |
855 | |
856 | $scalehtml .= '<th class="range">'. $grademin.'-'. $grademax.'</th>'; |
4ba9941c |
857 | } |
858 | $scalehtml .= '</tr>'; |
859 | } |
860 | return $scalehtml; |
861 | } |
862 | |
863 | /** |
864 | * Given a grade_category, grade_item or grade_grade, this function |
865 | * figures out the state of the object and builds then returns a div |
866 | * with the icons needed for the grader report. |
867 | * |
868 | * @param object $object |
4ba9941c |
869 | * @return string HTML |
870 | */ |
2cc773f5 |
871 | function get_icons($element) { |
872 | global $CFG, $USER; |
4ba9941c |
873 | |
2cc773f5 |
874 | if (!$USER->gradeediting[$this->courseid]) { |
875 | return '<div class="grade_icons" />'; |
79eabc2a |
876 | } |
4ba9941c |
877 | |
2cc773f5 |
878 | // Init all icons |
879 | $edit_icon = $this->gtree->get_edit_icon($element, $this->gpr); |
95d6df77 |
880 | $edit_calculation_icon = ''; |
2cc773f5 |
881 | $show_hide_icon = ''; |
882 | $contract_expand_icon = ''; |
883 | $lock_unlock_icon = ''; |
4ba9941c |
884 | |
2cc773f5 |
885 | if ($this->get_pref('showcalculations')) { |
886 | $edit_calculation_icon = $this->gtree->get_calculation_icon($element, $this->gpr); |
4ba9941c |
887 | } |
888 | |
2cc773f5 |
889 | if ($this->get_pref('showeyecons')) { |
890 | $show_hide_icon = $this->gtree->get_hiding_icon($element, $this->gpr); |
4ba9941c |
891 | } |
892 | |
2cc773f5 |
893 | if ($this->get_pref('showlocks')) { |
894 | $lock_unlock_icon = $this->gtree->get_locking_icon($element, $this->gpr); |
4ba9941c |
895 | } |
896 | |
2cc773f5 |
897 | // If object is a category, display expand/contract icon |
438a5aa9 |
898 | if ($element['type'] == 'category' && $this->get_pref('aggregationview', $element['object']->id) == GRADE_REPORT_AGGREGATION_VIEW_COMPACT) { |
2cc773f5 |
899 | // Load language strings |
900 | $strswitch_minus = $this->get_lang_string('contract', 'grades'); |
901 | $strswitch_plus = $this->get_lang_string('expand', 'grades'); |
902 | $expand_contract = 'switch_minus'; // Default: expanded |
48b5d8f3 |
903 | |
904 | $state = get_user_preferences('grade_report_categorystate' . $element['object']->id, GRADE_CATEGORY_EXPANDED); |
905 | |
2cc773f5 |
906 | if ($state == GRADE_CATEGORY_CONTRACTED) { |
907 | $expand_contract = 'switch_plus'; |
908 | } |
909 | $contract_expand_icon = '<a href="index.php?target=' . $element['eid'] |
4ba9941c |
910 | . "&action=$expand_contract" . $this->gtree->commonvars . "\">\n" |
911 | . '<img src="'.$CFG->pixpath.'/t/'.$expand_contract.'.gif" class="iconsmall" alt="' |
912 | . ${'str' . $expand_contract}.'" title="'.${'str' . $expand_contract}.'" /></a>'. "\n"; |
4ba9941c |
913 | } |
914 | |
2cc773f5 |
915 | return '<div class="grade_icons">'.$edit_icon.$edit_calculation_icon.$show_hide_icon.$lock_unlock_icon.$contract_expand_icon.'</div>'; |
4ba9941c |
916 | } |
48b5d8f3 |
917 | |
918 | /** |
919 | * Processes a single action against a category, grade_item or grade. |
920 | * @param string $target eid ({type}{id}, e.g. c4 for category4) |
921 | * @param string $action Which action to take (edit, delete etc...) |
922 | * @return |
923 | */ |
924 | function process_action($target, $action) { |
925 | $targettype = substr($target, 0, 1); |
926 | $targetid = substr($target, 1); |
927 | switch ($action) { |
928 | case 'switch_minus': |
929 | set_user_preference('grade_report_categorystate' . $targetid, GRADE_CATEGORY_CONTRACTED); |
930 | break; |
931 | case 'switch_plus': |
932 | set_user_preference('grade_report_categorystate' . $targetid, GRADE_CATEGORY_EXPANDED); |
933 | break; |
934 | default: |
935 | break; |
936 | } |
937 | |
938 | return true; |
939 | } |
4ba9941c |
940 | } |
941 | ?> |