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