1 |
efrain |
1 |
<?php
|
|
|
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/>.
|
|
|
16 |
|
|
|
17 |
namespace gradereport_user\report;
|
|
|
18 |
|
|
|
19 |
use cache;
|
|
|
20 |
use context_course;
|
|
|
21 |
use course_modinfo;
|
|
|
22 |
use grade_grade;
|
|
|
23 |
use grade_helper;
|
|
|
24 |
use grade_item;
|
|
|
25 |
use grade_report;
|
|
|
26 |
use grade_tree;
|
|
|
27 |
use html_writer;
|
|
|
28 |
use moodle_url;
|
|
|
29 |
|
|
|
30 |
defined('MOODLE_INTERNAL') || die;
|
|
|
31 |
|
|
|
32 |
global $CFG;
|
|
|
33 |
require_once($CFG->dirroot.'/grade/report/lib.php');
|
|
|
34 |
require_once($CFG->dirroot.'/grade/lib.php');
|
|
|
35 |
|
|
|
36 |
/**
|
|
|
37 |
* Class providing an API for the user report building and displaying.
|
|
|
38 |
* @uses grade_report
|
|
|
39 |
* @package gradereport_user
|
|
|
40 |
*/
|
|
|
41 |
class user extends grade_report {
|
|
|
42 |
|
|
|
43 |
/**
|
|
|
44 |
* A flexitable to hold the data.
|
|
|
45 |
* @var object $table
|
|
|
46 |
*/
|
|
|
47 |
public $table;
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* An array of table headers
|
|
|
51 |
* @var array
|
|
|
52 |
*/
|
|
|
53 |
public $tableheaders = [];
|
|
|
54 |
|
|
|
55 |
/**
|
|
|
56 |
* An array of table columns
|
|
|
57 |
* @var array
|
|
|
58 |
*/
|
|
|
59 |
public $tablecolumns = [];
|
|
|
60 |
|
|
|
61 |
/**
|
|
|
62 |
* An array containing rows of data for the table.
|
|
|
63 |
* @var array
|
|
|
64 |
*/
|
|
|
65 |
public $tabledata = [];
|
|
|
66 |
|
|
|
67 |
/**
|
|
|
68 |
* An array containing the grade items data for external usage (web services, ajax, etc...)
|
|
|
69 |
* @var array
|
|
|
70 |
*/
|
|
|
71 |
public $gradeitemsdata = [];
|
|
|
72 |
|
|
|
73 |
/**
|
|
|
74 |
* The grade tree structure
|
|
|
75 |
* @var grade_tree
|
|
|
76 |
*/
|
|
|
77 |
public $gtree;
|
|
|
78 |
|
|
|
79 |
/**
|
|
|
80 |
* Flat structure similar to grade tree
|
|
|
81 |
* @var void
|
|
|
82 |
*/
|
|
|
83 |
public $gseq;
|
|
|
84 |
|
|
|
85 |
/**
|
|
|
86 |
* show student ranks
|
|
|
87 |
* @var void
|
|
|
88 |
*/
|
|
|
89 |
public $showrank;
|
|
|
90 |
|
|
|
91 |
/**
|
|
|
92 |
* show grade percentages
|
|
|
93 |
* @var void
|
|
|
94 |
*/
|
|
|
95 |
public $showpercentage;
|
|
|
96 |
|
|
|
97 |
/**
|
|
|
98 |
* Show range
|
|
|
99 |
* @var bool
|
|
|
100 |
*/
|
|
|
101 |
public $showrange = true;
|
|
|
102 |
|
|
|
103 |
/**
|
|
|
104 |
* Show grades in the report, default true
|
|
|
105 |
* @var bool
|
|
|
106 |
*/
|
|
|
107 |
public $showgrade = true;
|
|
|
108 |
|
|
|
109 |
/**
|
|
|
110 |
* Decimal points to use for values in the report, default 2
|
|
|
111 |
* @var int
|
|
|
112 |
*/
|
|
|
113 |
public $decimals = 2;
|
|
|
114 |
|
|
|
115 |
/**
|
|
|
116 |
* The number of decimal places to round range to, default 0
|
|
|
117 |
* @var int
|
|
|
118 |
*/
|
|
|
119 |
public $rangedecimals = 0;
|
|
|
120 |
|
|
|
121 |
/**
|
|
|
122 |
* Show grade feedback in the report, default true
|
|
|
123 |
* @var bool
|
|
|
124 |
*/
|
|
|
125 |
public $showfeedback = true;
|
|
|
126 |
|
|
|
127 |
/**
|
|
|
128 |
* Show grade weighting in the report, default true.
|
|
|
129 |
* @var bool
|
|
|
130 |
*/
|
|
|
131 |
public $showweight = true;
|
|
|
132 |
|
|
|
133 |
/**
|
|
|
134 |
* Show letter grades in the report, default false
|
|
|
135 |
* @var bool
|
|
|
136 |
*/
|
|
|
137 |
public $showlettergrade = false;
|
|
|
138 |
|
|
|
139 |
/**
|
|
|
140 |
* Show the calculated contribution to the course total column.
|
|
|
141 |
* @var bool
|
|
|
142 |
*/
|
|
|
143 |
public $showcontributiontocoursetotal = true;
|
|
|
144 |
|
|
|
145 |
/**
|
|
|
146 |
* Show average grades in the report, default false.
|
|
|
147 |
* @var false
|
|
|
148 |
*/
|
|
|
149 |
public $showaverage = false;
|
|
|
150 |
|
|
|
151 |
/**
|
|
|
152 |
* @var int
|
|
|
153 |
*/
|
|
|
154 |
public $maxdepth;
|
|
|
155 |
/**
|
|
|
156 |
* @var void
|
|
|
157 |
*/
|
|
|
158 |
public $evenodd;
|
|
|
159 |
|
|
|
160 |
/**
|
|
|
161 |
* @var bool
|
|
|
162 |
*/
|
|
|
163 |
public $canviewhidden;
|
|
|
164 |
|
|
|
165 |
/**
|
|
|
166 |
* @var string|null
|
|
|
167 |
*/
|
|
|
168 |
public $switch;
|
|
|
169 |
|
|
|
170 |
/**
|
|
|
171 |
* Show hidden items even when user does not have required cap
|
|
|
172 |
* @var void
|
|
|
173 |
*/
|
|
|
174 |
public $showhiddenitems;
|
|
|
175 |
|
|
|
176 |
/**
|
|
|
177 |
* @var string
|
|
|
178 |
*/
|
|
|
179 |
public $baseurl;
|
|
|
180 |
/**
|
|
|
181 |
* @var string
|
|
|
182 |
*/
|
|
|
183 |
public $pbarurl;
|
|
|
184 |
|
|
|
185 |
/**
|
|
|
186 |
* The modinfo object to be used.
|
|
|
187 |
*
|
|
|
188 |
* @var course_modinfo
|
|
|
189 |
*/
|
|
|
190 |
protected $modinfo = null;
|
|
|
191 |
|
|
|
192 |
/**
|
|
|
193 |
* View as user.
|
|
|
194 |
*
|
|
|
195 |
* When this is set to true, the visibility checks, and capability checks will be
|
|
|
196 |
* applied to the user whose grades are being displayed. This is very useful when
|
|
|
197 |
* a mentor/parent is viewing the report of their mentee because they need to have
|
|
|
198 |
* access to the same information, but not more, not less.
|
|
|
199 |
*
|
|
|
200 |
* @var boolean
|
|
|
201 |
*/
|
|
|
202 |
protected $viewasuser = false;
|
|
|
203 |
|
|
|
204 |
/**
|
|
|
205 |
* An array that collects the aggregationhints for every
|
|
|
206 |
* grade_item. The hints contain grade, grademin, grademax
|
|
|
207 |
* status, weight and parent.
|
|
|
208 |
*
|
|
|
209 |
* @var array
|
|
|
210 |
*/
|
|
|
211 |
protected $aggregationhints = [];
|
|
|
212 |
|
|
|
213 |
/**
|
|
|
214 |
* Used for proper column indentation.
|
|
|
215 |
* @var int
|
|
|
216 |
*/
|
|
|
217 |
public $columncount = 0;
|
|
|
218 |
|
|
|
219 |
/**
|
|
|
220 |
* Constructor. Sets local copies of user preferences and initialises grade_tree.
|
|
|
221 |
* @param int $courseid
|
|
|
222 |
* @param null|object $gpr grade plugin return tracking object
|
|
|
223 |
* @param object $context
|
|
|
224 |
* @param int $userid The id of the user
|
|
|
225 |
* @param bool $viewasuser Set this to true when the current user is a mentor/parent of the targetted user.
|
|
|
226 |
*/
|
|
|
227 |
public function __construct(int $courseid, ?object $gpr, object $context, int $userid, bool $viewasuser = null) {
|
|
|
228 |
global $DB, $CFG;
|
|
|
229 |
parent::__construct($courseid, $gpr, $context);
|
|
|
230 |
|
|
|
231 |
$this->showrank = grade_get_setting($this->courseid, 'report_user_showrank', $CFG->grade_report_user_showrank);
|
|
|
232 |
$this->showpercentage = grade_get_setting(
|
|
|
233 |
$this->courseid,
|
|
|
234 |
'report_user_showpercentage',
|
|
|
235 |
$CFG->grade_report_user_showpercentage
|
|
|
236 |
);
|
|
|
237 |
$this->showhiddenitems = grade_get_setting(
|
|
|
238 |
$this->courseid,
|
|
|
239 |
'report_user_showhiddenitems',
|
|
|
240 |
$CFG->grade_report_user_showhiddenitems
|
|
|
241 |
);
|
|
|
242 |
$this->showtotalsifcontainhidden = [$this->courseid => grade_get_setting(
|
|
|
243 |
$this->courseid,
|
|
|
244 |
'report_user_showtotalsifcontainhidden',
|
|
|
245 |
$CFG->grade_report_user_showtotalsifcontainhidden
|
|
|
246 |
)];
|
|
|
247 |
|
|
|
248 |
$this->showgrade = grade_get_setting(
|
|
|
249 |
$this->courseid,
|
|
|
250 |
'report_user_showgrade',
|
|
|
251 |
!empty($CFG->grade_report_user_showgrade)
|
|
|
252 |
);
|
|
|
253 |
$this->showrange = grade_get_setting(
|
|
|
254 |
$this->courseid,
|
|
|
255 |
'report_user_showrange',
|
|
|
256 |
!empty($CFG->grade_report_user_showrange)
|
|
|
257 |
);
|
|
|
258 |
$this->showfeedback = grade_get_setting(
|
|
|
259 |
$this->courseid,
|
|
|
260 |
'report_user_showfeedback',
|
|
|
261 |
!empty($CFG->grade_report_user_showfeedback)
|
|
|
262 |
);
|
|
|
263 |
|
|
|
264 |
$this->showweight = grade_get_setting($this->courseid, 'report_user_showweight',
|
|
|
265 |
!empty($CFG->grade_report_user_showweight));
|
|
|
266 |
|
|
|
267 |
$this->showcontributiontocoursetotal = grade_get_setting($this->courseid, 'report_user_showcontributiontocoursetotal',
|
|
|
268 |
!empty($CFG->grade_report_user_showcontributiontocoursetotal));
|
|
|
269 |
|
|
|
270 |
$this->showlettergrade = grade_get_setting(
|
|
|
271 |
$this->courseid,
|
|
|
272 |
'report_user_showlettergrade',
|
|
|
273 |
!empty($CFG->grade_report_user_showlettergrade)
|
|
|
274 |
);
|
|
|
275 |
$this->showaverage = grade_get_setting(
|
|
|
276 |
$this->courseid,
|
|
|
277 |
'report_user_showaverage',
|
|
|
278 |
!empty($CFG->grade_report_user_showaverage)
|
|
|
279 |
);
|
|
|
280 |
|
|
|
281 |
$this->viewasuser = $viewasuser;
|
|
|
282 |
|
|
|
283 |
// The default grade decimals is 2.
|
|
|
284 |
$defaultdecimals = 2;
|
|
|
285 |
if (property_exists($CFG, 'grade_decimalpoints')) {
|
|
|
286 |
$defaultdecimals = $CFG->grade_decimalpoints;
|
|
|
287 |
}
|
|
|
288 |
$this->decimals = grade_get_setting($this->courseid, 'decimalpoints', $defaultdecimals);
|
|
|
289 |
|
|
|
290 |
// The default range decimals is 0.
|
|
|
291 |
$defaultrangedecimals = 0;
|
|
|
292 |
if (property_exists($CFG, 'grade_report_user_rangedecimals')) {
|
|
|
293 |
$defaultrangedecimals = $CFG->grade_report_user_rangedecimals;
|
|
|
294 |
}
|
|
|
295 |
$this->rangedecimals = grade_get_setting($this->courseid, 'report_user_rangedecimals', $defaultrangedecimals);
|
|
|
296 |
|
|
|
297 |
$this->switch = grade_get_setting($this->courseid, 'aggregationposition', $CFG->grade_aggregationposition);
|
|
|
298 |
|
|
|
299 |
// Grab the grade_tree for this course.
|
|
|
300 |
$this->gtree = new grade_tree($this->courseid, false, $this->switch, null, !$CFG->enableoutcomes);
|
|
|
301 |
|
|
|
302 |
// Get the user (for full name).
|
|
|
303 |
$this->user = $DB->get_record('user', ['id' => $userid]);
|
|
|
304 |
|
|
|
305 |
// What user are we viewing this as?
|
|
|
306 |
$coursecontext = context_course::instance($this->courseid);
|
|
|
307 |
if ($viewasuser) {
|
|
|
308 |
$this->modinfo = new course_modinfo($this->course, $this->user->id);
|
|
|
309 |
$this->canviewhidden = has_capability('moodle/grade:viewhidden', $coursecontext, $this->user->id);
|
|
|
310 |
} else {
|
|
|
311 |
$this->modinfo = $this->gtree->modinfo;
|
|
|
312 |
$this->canviewhidden = has_capability('moodle/grade:viewhidden', $coursecontext);
|
|
|
313 |
}
|
|
|
314 |
|
|
|
315 |
// Determine the number of rows and indentation.
|
|
|
316 |
$this->maxdepth = 1;
|
|
|
317 |
$this->inject_rowspans($this->gtree->top_element);
|
|
|
318 |
$this->maxdepth++; // Need to account for the lead column that spans all children.
|
|
|
319 |
for ($i = 1; $i <= $this->maxdepth; $i++) {
|
|
|
320 |
$this->evenodd[$i] = 0;
|
|
|
321 |
}
|
|
|
322 |
|
|
|
323 |
$this->tabledata = [];
|
|
|
324 |
|
|
|
325 |
// The base url for sorting by first/last name.
|
|
|
326 |
$this->baseurl = new \moodle_url('/grade/report', ['id' => $courseid, 'userid' => $userid]);
|
|
|
327 |
$this->pbarurl = $this->baseurl;
|
|
|
328 |
|
|
|
329 |
// There no groups on this report - rank is from all course users.
|
|
|
330 |
$this->setup_table();
|
|
|
331 |
|
|
|
332 |
// Optionally calculate grade item averages.
|
|
|
333 |
if ($this->showaverage) {
|
|
|
334 |
$cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'gradereport_user', 'averages');
|
|
|
335 |
$avg = $cache->get(get_class($this));
|
|
|
336 |
if (!$avg) {
|
|
|
337 |
$showonlyactiveenrol = $this->show_only_active();
|
|
|
338 |
$ungradedcounts = $this->ungraded_counts(false, false, $showonlyactiveenrol);
|
|
|
339 |
$this->columncount = 0;
|
|
|
340 |
$avgrow = $this->format_averages($ungradedcounts);
|
|
|
341 |
// Save to cache.
|
|
|
342 |
$cache->set(get_class($this), $avgrow->cells);
|
|
|
343 |
}
|
|
|
344 |
}
|
|
|
345 |
|
|
|
346 |
}
|
|
|
347 |
|
|
|
348 |
/**
|
|
|
349 |
* Returns a row of grade items averages
|
|
|
350 |
*
|
|
|
351 |
* @param grade_item $gradeitem Grade item.
|
|
|
352 |
* @param array|null $aggr Average value and meancount information.
|
|
|
353 |
* @param bool|null $shownumberofgrades Whether to show number of grades.
|
|
|
354 |
* @return \html_table_cell Formatted average cell.
|
|
|
355 |
*/
|
|
|
356 |
protected function format_average_cell(grade_item $gradeitem, ?array $aggr = null, ?bool $shownumberofgrades = null): \html_table_cell {
|
|
|
357 |
|
|
|
358 |
if ($gradeitem->needsupdate) {
|
|
|
359 |
$avg = '<td class="cell c' . $this->columncount++.'">' .
|
|
|
360 |
'<span class="gradingerror">' . get_string('error').'</span></td>';
|
|
|
361 |
} else {
|
|
|
362 |
if (empty($aggr['average'])) {
|
|
|
363 |
$avg = '-';
|
|
|
364 |
} else {
|
|
|
365 |
$numberofgrades = '';
|
|
|
366 |
if ($shownumberofgrades) {
|
|
|
367 |
$numberofgrades = " (" . $aggr['meancount'] . ")";
|
|
|
368 |
}
|
|
|
369 |
$avg = $aggr['average'] . $numberofgrades;
|
|
|
370 |
}
|
|
|
371 |
}
|
|
|
372 |
return new \html_table_cell($avg);
|
|
|
373 |
}
|
|
|
374 |
|
|
|
375 |
/**
|
|
|
376 |
* Recurse through a tree of elements setting the rowspan property on each element
|
|
|
377 |
*
|
|
|
378 |
* @param array $element Either the top element or, during recursion, the current element
|
|
|
379 |
* @return int The number of elements processed
|
|
|
380 |
*/
|
|
|
381 |
public function inject_rowspans(array &$element): int {
|
|
|
382 |
|
|
|
383 |
if ($element['depth'] > $this->maxdepth) {
|
|
|
384 |
$this->maxdepth = $element['depth'];
|
|
|
385 |
}
|
|
|
386 |
if (empty($element['children'])) {
|
|
|
387 |
return 1;
|
|
|
388 |
}
|
|
|
389 |
$count = 1;
|
|
|
390 |
|
|
|
391 |
foreach ($element['children'] as $key => $child) {
|
|
|
392 |
// If category is hidden then do not include it in the rowspan.
|
|
|
393 |
if ($child['type'] == 'category' && $child['object']->is_hidden() && !$this->canviewhidden
|
|
|
394 |
&& ($this->showhiddenitems == GRADE_REPORT_USER_HIDE_HIDDEN
|
|
|
395 |
|| ($this->showhiddenitems == GRADE_REPORT_USER_HIDE_UNTIL && !$child['object']->is_hiddenuntil()))) {
|
|
|
396 |
// Just calculate the rowspans for children of this category, don't add them to the count.
|
|
|
397 |
$this->inject_rowspans($element['children'][$key]);
|
|
|
398 |
} else {
|
|
|
399 |
$count += $this->inject_rowspans($element['children'][$key]);
|
|
|
400 |
// Take into consideration the addition of a new row (where the rowspan is defined) right after a category row.
|
|
|
401 |
if ($child['type'] == 'category') {
|
|
|
402 |
$count += 1;
|
|
|
403 |
}
|
|
|
404 |
|
|
|
405 |
}
|
|
|
406 |
}
|
|
|
407 |
|
|
|
408 |
$element['rowspan'] = $count;
|
|
|
409 |
return $count;
|
|
|
410 |
}
|
|
|
411 |
|
|
|
412 |
|
|
|
413 |
/**
|
|
|
414 |
* Prepares the headers and attributes of the flexitable.
|
|
|
415 |
*/
|
|
|
416 |
public function setup_table() {
|
|
|
417 |
/*
|
|
|
418 |
* Table has 1-8 columns
|
|
|
419 |
*| All columns except for itemname/description are optional
|
|
|
420 |
*/
|
|
|
421 |
|
|
|
422 |
// Setting up table headers.
|
|
|
423 |
|
|
|
424 |
$this->tablecolumns = ['itemname'];
|
|
|
425 |
$this->tableheaders = [get_string('gradeitem', 'grades')];
|
|
|
426 |
|
|
|
427 |
if ($this->showweight) {
|
|
|
428 |
$this->tablecolumns[] = 'weight';
|
|
|
429 |
$this->tableheaders[] = get_string('weightuc', 'grades');
|
|
|
430 |
}
|
|
|
431 |
|
|
|
432 |
if ($this->showgrade) {
|
|
|
433 |
$this->tablecolumns[] = 'grade';
|
|
|
434 |
$this->tableheaders[] = get_string('gradenoun');
|
|
|
435 |
}
|
|
|
436 |
|
|
|
437 |
if ($this->showrange) {
|
|
|
438 |
$this->tablecolumns[] = 'range';
|
|
|
439 |
$this->tableheaders[] = get_string('range', 'grades');
|
|
|
440 |
}
|
|
|
441 |
|
|
|
442 |
if ($this->showpercentage) {
|
|
|
443 |
$this->tablecolumns[] = 'percentage';
|
|
|
444 |
$this->tableheaders[] = get_string('percentage', 'grades');
|
|
|
445 |
}
|
|
|
446 |
|
|
|
447 |
if ($this->showlettergrade) {
|
|
|
448 |
$this->tablecolumns[] = 'lettergrade';
|
|
|
449 |
$this->tableheaders[] = get_string('lettergrade', 'grades');
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
if ($this->showrank) {
|
|
|
453 |
$this->tablecolumns[] = 'rank';
|
|
|
454 |
$this->tableheaders[] = get_string('rank', 'grades');
|
|
|
455 |
}
|
|
|
456 |
|
|
|
457 |
if ($this->showaverage) {
|
|
|
458 |
$this->tablecolumns[] = 'average';
|
|
|
459 |
$this->tableheaders[] = get_string('average', 'grades');
|
|
|
460 |
}
|
|
|
461 |
|
|
|
462 |
if ($this->showfeedback) {
|
|
|
463 |
$this->tablecolumns[] = 'feedback';
|
|
|
464 |
$this->tableheaders[] = get_string('feedback', 'grades');
|
|
|
465 |
}
|
|
|
466 |
|
|
|
467 |
if ($this->showcontributiontocoursetotal) {
|
|
|
468 |
$this->tablecolumns[] = 'contributiontocoursetotal';
|
|
|
469 |
$this->tableheaders[] = get_string('contributiontocoursetotal', 'grades');
|
|
|
470 |
}
|
|
|
471 |
}
|
|
|
472 |
|
|
|
473 |
/**
|
|
|
474 |
* Provide an entry point to build the table.
|
|
|
475 |
*
|
|
|
476 |
* @return bool
|
|
|
477 |
*/
|
|
|
478 |
public function fill_table(): bool {
|
|
|
479 |
$this->fill_table_recursive($this->gtree->top_element);
|
|
|
480 |
return true;
|
|
|
481 |
}
|
|
|
482 |
|
|
|
483 |
/**
|
|
|
484 |
* Fill the table with data.
|
|
|
485 |
*
|
|
|
486 |
* @param array $element - The table data for the current row.
|
|
|
487 |
*/
|
|
|
488 |
private function fill_table_recursive(array &$element) {
|
|
|
489 |
global $DB, $CFG, $OUTPUT;
|
|
|
490 |
|
|
|
491 |
$type = $element['type'];
|
|
|
492 |
$depth = $element['depth'];
|
|
|
493 |
$gradeobject = $element['object'];
|
|
|
494 |
$eid = $gradeobject->id;
|
|
|
495 |
$element['userid'] = $userid = $this->user->id;
|
|
|
496 |
$fullname = grade_helper::get_element_header($element, true, false, true, false, true);
|
|
|
497 |
$data = [];
|
|
|
498 |
$gradeitemdata = [];
|
|
|
499 |
$hidden = '';
|
|
|
500 |
$excluded = '';
|
|
|
501 |
$itemlevel = ($type == 'categoryitem' || $type == 'category' || $type == 'courseitem') ? $depth : ($depth + 1);
|
|
|
502 |
$class = 'level' . $itemlevel;
|
|
|
503 |
$classfeedback = '';
|
|
|
504 |
$rowspandata = [];
|
|
|
505 |
|
|
|
506 |
// If this is a hidden grade category, hide it completely from the user.
|
|
|
507 |
if ($type == 'category' && $gradeobject->is_hidden() && !$this->canviewhidden && (
|
|
|
508 |
$this->showhiddenitems == GRADE_REPORT_USER_HIDE_HIDDEN ||
|
|
|
509 |
($this->showhiddenitems == GRADE_REPORT_USER_HIDE_UNTIL && !$gradeobject->is_hiddenuntil()))) {
|
|
|
510 |
return false;
|
|
|
511 |
}
|
|
|
512 |
|
|
|
513 |
// Process those items that have scores associated.
|
|
|
514 |
if ($type == 'item' || $type == 'categoryitem' || $type == 'courseitem') {
|
|
|
515 |
$headerrow = "row_{$eid}_{$this->user->id}";
|
|
|
516 |
$headercat = "cat_{$gradeobject->categoryid}_{$this->user->id}";
|
|
|
517 |
|
|
|
518 |
if (! $gradegrade = grade_grade::fetch(['itemid' => $gradeobject->id, 'userid' => $this->user->id])) {
|
|
|
519 |
$gradegrade = new grade_grade();
|
|
|
520 |
$gradegrade->userid = $this->user->id;
|
|
|
521 |
$gradegrade->itemid = $gradeobject->id;
|
|
|
522 |
}
|
|
|
523 |
|
|
|
524 |
$gradegrade->load_grade_item();
|
|
|
525 |
|
|
|
526 |
// Hidden Items.
|
|
|
527 |
if ($gradegrade->grade_item->is_hidden()) {
|
|
|
528 |
$hidden = ' dimmed_text';
|
|
|
529 |
}
|
|
|
530 |
|
|
|
531 |
$hide = false;
|
|
|
532 |
// If this is a hidden grade item, hide it completely from the user.
|
|
|
533 |
if ($gradegrade->is_hidden() && !$this->canviewhidden && (
|
|
|
534 |
$this->showhiddenitems == GRADE_REPORT_USER_HIDE_HIDDEN ||
|
|
|
535 |
($this->showhiddenitems == GRADE_REPORT_USER_HIDE_UNTIL && !$gradegrade->is_hiddenuntil()))) {
|
|
|
536 |
$hide = true;
|
|
|
537 |
} else if (!empty($gradeobject->itemmodule) && !empty($gradeobject->iteminstance)) {
|
|
|
538 |
// The grade object can be marked visible but still be hidden if
|
|
|
539 |
// the student cannot see the activity due to conditional access
|
|
|
540 |
// and it's set to be hidden entirely.
|
|
|
541 |
$instances = $this->modinfo->get_instances_of($gradeobject->itemmodule);
|
|
|
542 |
if (!empty($instances[$gradeobject->iteminstance])) {
|
|
|
543 |
$cm = $instances[$gradeobject->iteminstance];
|
|
|
544 |
$gradeitemdata['cmid'] = $cm->id;
|
|
|
545 |
if (!$cm->uservisible) {
|
|
|
546 |
// If there is 'availableinfo' text then it is only greyed
|
|
|
547 |
// out and not entirely hidden.
|
|
|
548 |
if (!$cm->availableinfo) {
|
|
|
549 |
$hide = true;
|
|
|
550 |
}
|
|
|
551 |
}
|
|
|
552 |
}
|
|
|
553 |
}
|
|
|
554 |
|
|
|
555 |
// Actual Grade - We need to calculate this whether the row is hidden or not.
|
|
|
556 |
$gradeval = $gradegrade->finalgrade;
|
|
|
557 |
$hint = $gradegrade->get_aggregation_hint();
|
|
|
558 |
if (!$this->canviewhidden) {
|
|
|
559 |
// Virtual Grade (may be calculated excluding hidden items etc).
|
|
|
560 |
$adjustedgrade = $this->blank_hidden_total_and_adjust_bounds($this->courseid,
|
|
|
561 |
$gradegrade->grade_item,
|
|
|
562 |
$gradeval);
|
|
|
563 |
|
|
|
564 |
$gradeval = $adjustedgrade['grade'];
|
|
|
565 |
|
|
|
566 |
// We temporarily adjust the view of this grade item - because the min and
|
|
|
567 |
// max are affected by the hidden values in the aggregation.
|
|
|
568 |
$gradegrade->grade_item->grademax = $adjustedgrade['grademax'];
|
|
|
569 |
$gradegrade->grade_item->grademin = $adjustedgrade['grademin'];
|
|
|
570 |
$hint['status'] = $adjustedgrade['aggregationstatus'];
|
|
|
571 |
$hint['weight'] = $adjustedgrade['aggregationweight'];
|
|
|
572 |
} else {
|
|
|
573 |
// The max and min for an aggregation may be different to the grade_item.
|
|
|
574 |
if (!is_null($gradeval)) {
|
|
|
575 |
$gradegrade->grade_item->grademax = $gradegrade->get_grade_max();
|
|
|
576 |
$gradegrade->grade_item->grademin = $gradegrade->get_grade_min();
|
|
|
577 |
}
|
|
|
578 |
}
|
|
|
579 |
|
|
|
580 |
if (!$hide) {
|
|
|
581 |
$canviewall = has_capability('moodle/grade:viewall', $this->context);
|
|
|
582 |
// Other class information.
|
|
|
583 |
$class .= $hidden . $excluded;
|
|
|
584 |
// Alter style based on whether aggregation is first or last.
|
|
|
585 |
if ($this->switch) {
|
|
|
586 |
$class .= ($type == 'categoryitem' || $type == 'courseitem') ? " d$depth baggt b2b" : " item b1b";
|
|
|
587 |
} else {
|
|
|
588 |
$class .= ($type == 'categoryitem' || $type == 'courseitem') ? " d$depth baggb" : " item b1b";
|
|
|
589 |
}
|
|
|
590 |
|
|
|
591 |
$itemicon = \html_writer::div(grade_helper::get_element_icon($element), 'mr-1');
|
|
|
592 |
$elementtype = grade_helper::get_element_type_string($element);
|
|
|
593 |
$itemtype = \html_writer::span($elementtype, 'd-block text-uppercase small dimmed_text',
|
|
|
594 |
['title' => $elementtype]);
|
|
|
595 |
|
|
|
596 |
if ($type == 'categoryitem' || $type == 'courseitem') {
|
|
|
597 |
$headercat = "cat_{$gradeobject->iteminstance}_{$this->user->id}";
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
// Generate the content for a cell that represents a grade item.
|
|
|
601 |
$itemtitle = \html_writer::div($fullname, 'rowtitle');
|
|
|
602 |
$content = \html_writer::div($itemtype . $itemtitle);
|
|
|
603 |
|
|
|
604 |
// Name.
|
|
|
605 |
$data['itemname']['content'] = \html_writer::div($itemicon . $content, "{$type} d-flex align-items-center");
|
|
|
606 |
$data['itemname']['class'] = $class;
|
|
|
607 |
$data['itemname']['colspan'] = ($this->maxdepth - $depth);
|
|
|
608 |
$data['itemname']['id'] = $headerrow;
|
|
|
609 |
|
|
|
610 |
// Basic grade item information.
|
|
|
611 |
$gradeitemdata['id'] = $gradeobject->id;
|
|
|
612 |
$gradeitemdata['itemname'] = $gradeobject->itemname;
|
|
|
613 |
$gradeitemdata['itemtype'] = $gradeobject->itemtype;
|
|
|
614 |
$gradeitemdata['itemmodule'] = $gradeobject->itemmodule;
|
|
|
615 |
$gradeitemdata['iteminstance'] = $gradeobject->iteminstance;
|
|
|
616 |
$gradeitemdata['itemnumber'] = $gradeobject->itemnumber;
|
|
|
617 |
$gradeitemdata['idnumber'] = $gradeobject->idnumber;
|
|
|
618 |
$gradeitemdata['categoryid'] = $gradeobject->categoryid;
|
|
|
619 |
$gradeitemdata['outcomeid'] = $gradeobject->outcomeid;
|
|
|
620 |
$gradeitemdata['scaleid'] = $gradeobject->outcomeid;
|
|
|
621 |
$gradeitemdata['locked'] = $canviewall ? $gradegrade->grade_item->is_locked() : null;
|
|
|
622 |
|
|
|
623 |
if ($this->showfeedback) {
|
|
|
624 |
// Copy $class before appending itemcenter as feedback should not be centered.
|
|
|
625 |
$classfeedback = $class;
|
|
|
626 |
}
|
|
|
627 |
$class .= " itemcenter ";
|
|
|
628 |
if ($this->showweight) {
|
|
|
629 |
$data['weight']['class'] = $class;
|
|
|
630 |
$data['weight']['content'] = '-';
|
|
|
631 |
$data['weight']['headers'] = "$headercat $headerrow weight$userid";
|
|
|
632 |
// Has a weight assigned, might be extra credit.
|
|
|
633 |
|
|
|
634 |
// This obliterates the weight because it provides a more informative description.
|
|
|
635 |
if (is_numeric($hint['weight'])) {
|
|
|
636 |
$data['weight']['content'] = format_float($hint['weight'] * 100.0, 2) . ' %';
|
|
|
637 |
$gradeitemdata['weightraw'] = $hint['weight'];
|
|
|
638 |
$gradeitemdata['weightformatted'] = $data['weight']['content'];
|
|
|
639 |
}
|
|
|
640 |
if ($hint['status'] != 'used' && $hint['status'] != 'unknown') {
|
|
|
641 |
$data['weight']['content'] .= '<br>' . get_string('aggregationhint' . $hint['status'], 'grades');
|
|
|
642 |
$gradeitemdata['status'] = $hint['status'];
|
|
|
643 |
}
|
|
|
644 |
}
|
|
|
645 |
|
|
|
646 |
if ($this->showgrade) {
|
|
|
647 |
$gradestatus = '';
|
|
|
648 |
// We only show status icons for a teacher if he views report as himself.
|
|
|
649 |
if (isset($this->viewasuser) && !$this->viewasuser) {
|
|
|
650 |
$context = [
|
|
|
651 |
'hidden' => $gradegrade->is_hidden(),
|
|
|
652 |
'locked' => $gradegrade->is_locked(),
|
|
|
653 |
'overridden' => $gradegrade->is_overridden(),
|
|
|
654 |
'excluded' => $gradegrade->is_excluded()
|
|
|
655 |
];
|
|
|
656 |
|
|
|
657 |
if (in_array(true, $context)) {
|
|
|
658 |
$context['classes'] = 'gradestatus';
|
|
|
659 |
$gradestatus = $OUTPUT->render_from_template('core_grades/status_icons', $context);
|
|
|
660 |
}
|
|
|
661 |
}
|
|
|
662 |
|
|
|
663 |
$gradeitemdata['graderaw'] = null;
|
|
|
664 |
$gradeitemdata['gradehiddenbydate'] = false;
|
|
|
665 |
$gradeitemdata['gradeneedsupdate'] = $gradegrade->grade_item->needsupdate;
|
|
|
666 |
$gradeitemdata['gradeishidden'] = $gradegrade->is_hidden();
|
|
|
667 |
$gradeitemdata['gradedatesubmitted'] = $gradegrade->get_datesubmitted();
|
|
|
668 |
$gradeitemdata['gradedategraded'] = $gradegrade->get_dategraded();
|
|
|
669 |
$gradeitemdata['gradeislocked'] = $canviewall ? $gradegrade->is_locked() : null;
|
|
|
670 |
$gradeitemdata['gradeisoverridden'] = $canviewall ? $gradegrade->is_overridden() : null;
|
|
|
671 |
|
|
|
672 |
if ($gradegrade->grade_item->needsupdate) {
|
|
|
673 |
$data['grade']['class'] = $class.' gradingerror';
|
|
|
674 |
$data['grade']['content'] = get_string('error');
|
|
|
675 |
} else if (
|
|
|
676 |
!empty($CFG->grade_hiddenasdate)
|
|
|
677 |
&& $gradegrade->get_datesubmitted()
|
|
|
678 |
&& !$this->canviewhidden
|
|
|
679 |
&& $gradegrade->is_hidden()
|
|
|
680 |
&& !$gradegrade->grade_item->is_category_item()
|
|
|
681 |
&& !$gradegrade->grade_item->is_course_item()
|
|
|
682 |
) {
|
|
|
683 |
// The problem here is that we do not have the time when grade value was modified
|
|
|
684 |
// 'timemodified' is general modification date for grade_grades records.
|
|
|
685 |
$class .= ' datesubmitted';
|
|
|
686 |
$data['grade']['class'] = $class;
|
|
|
687 |
$data['grade']['content'] = get_string(
|
|
|
688 |
'submittedon',
|
|
|
689 |
'grades',
|
|
|
690 |
userdate(
|
|
|
691 |
$gradegrade->get_datesubmitted(),
|
|
|
692 |
get_string('strftimedatetimeshort')
|
|
|
693 |
) . $gradestatus
|
|
|
694 |
);
|
|
|
695 |
$gradeitemdata['gradehiddenbydate'] = true;
|
|
|
696 |
} else if ($gradegrade->is_hidden()) {
|
|
|
697 |
$data['grade']['class'] = $class.' dimmed_text';
|
|
|
698 |
$data['grade']['content'] = '-';
|
|
|
699 |
|
|
|
700 |
if ($this->canviewhidden) {
|
|
|
701 |
$gradeitemdata['graderaw'] = $gradeval;
|
|
|
702 |
$data['grade']['content'] = grade_format_gradevalue($gradeval,
|
|
|
703 |
$gradegrade->grade_item,
|
|
|
704 |
true) . $gradestatus;
|
|
|
705 |
}
|
|
|
706 |
} else {
|
|
|
707 |
$gradestatusclass = '';
|
|
|
708 |
$gradepassicon = '';
|
|
|
709 |
$ispassinggrade = $gradegrade->is_passed($gradegrade->grade_item);
|
|
|
710 |
if (!is_null($gradeval) && !is_null($ispassinggrade)) {
|
|
|
711 |
$gradestatusclass = $ispassinggrade ? 'gradepass' : 'gradefail';
|
|
|
712 |
if ($ispassinggrade) {
|
|
|
713 |
$gradepassicon = $OUTPUT->pix_icon(
|
|
|
714 |
'i/valid',
|
|
|
715 |
get_string('pass', 'grades'),
|
|
|
716 |
null,
|
|
|
717 |
['class' => 'inline']
|
|
|
718 |
);
|
|
|
719 |
} else {
|
|
|
720 |
$gradepassicon = $OUTPUT->pix_icon(
|
|
|
721 |
'i/invalid',
|
|
|
722 |
get_string('fail', 'grades'),
|
|
|
723 |
null,
|
|
|
724 |
['class' => 'inline']
|
|
|
725 |
);
|
|
|
726 |
}
|
|
|
727 |
}
|
|
|
728 |
|
|
|
729 |
$data['grade']['class'] = "{$class} {$gradestatusclass}";
|
|
|
730 |
$data['grade']['content'] = $gradepassicon . grade_format_gradevalue($gradeval,
|
|
|
731 |
$gradegrade->grade_item, true) . $gradestatus;
|
|
|
732 |
$gradeitemdata['graderaw'] = $gradeval;
|
|
|
733 |
}
|
|
|
734 |
$data['grade']['headers'] = "$headercat $headerrow grade$userid";
|
|
|
735 |
$gradeitemdata['gradeformatted'] = $data['grade']['content'];
|
|
|
736 |
// If the current grade item need to show a grade action menu, generate the appropriate output.
|
|
|
737 |
if ($gradeactionmenu = $this->gtree->get_grade_action_menu($gradegrade)) {
|
|
|
738 |
$gradecontainer = html_writer::div($data['grade']['content']);
|
|
|
739 |
$grademenucontainer = html_writer::div($gradeactionmenu, 'pl-1 d-flex align-items-center');
|
|
|
740 |
$data['grade']['content'] = html_writer::div($gradecontainer . $grademenucontainer,
|
|
|
741 |
'd-flex align-items-center');
|
|
|
742 |
}
|
|
|
743 |
}
|
|
|
744 |
|
|
|
745 |
// Range.
|
|
|
746 |
if ($this->showrange) {
|
|
|
747 |
$data['range']['class'] = $class;
|
|
|
748 |
$data['range']['content'] = $gradegrade->grade_item->get_formatted_range(
|
|
|
749 |
GRADE_DISPLAY_TYPE_REAL,
|
|
|
750 |
$this->rangedecimals
|
|
|
751 |
);
|
|
|
752 |
$data['range']['headers'] = "$headercat $headerrow range$userid";
|
|
|
753 |
|
|
|
754 |
$gradeitemdata['rangeformatted'] = $data['range']['content'];
|
|
|
755 |
$gradeitemdata['grademin'] = $gradegrade->grade_item->grademin;
|
|
|
756 |
$gradeitemdata['grademax'] = $gradegrade->grade_item->grademax;
|
|
|
757 |
}
|
|
|
758 |
|
|
|
759 |
// Percentage.
|
|
|
760 |
if ($this->showpercentage) {
|
|
|
761 |
if ($gradegrade->grade_item->needsupdate) {
|
|
|
762 |
$data['percentage']['class'] = $class.' gradingerror';
|
|
|
763 |
$data['percentage']['content'] = get_string('error');
|
|
|
764 |
} else if ($gradegrade->is_hidden()) {
|
|
|
765 |
$data['percentage']['class'] = $class.' dimmed_text';
|
|
|
766 |
$data['percentage']['content'] = '-';
|
|
|
767 |
if ($this->canviewhidden) {
|
|
|
768 |
$data['percentage']['content'] = grade_format_gradevalue(
|
|
|
769 |
$gradeval,
|
|
|
770 |
$gradegrade->grade_item,
|
|
|
771 |
true,
|
|
|
772 |
GRADE_DISPLAY_TYPE_PERCENTAGE
|
|
|
773 |
);
|
|
|
774 |
}
|
|
|
775 |
} else {
|
|
|
776 |
$data['percentage']['class'] = $class;
|
|
|
777 |
$data['percentage']['content'] = grade_format_gradevalue(
|
|
|
778 |
$gradeval,
|
|
|
779 |
$gradegrade->grade_item,
|
|
|
780 |
true,
|
|
|
781 |
GRADE_DISPLAY_TYPE_PERCENTAGE
|
|
|
782 |
);
|
|
|
783 |
}
|
|
|
784 |
$data['percentage']['headers'] = "$headercat $headerrow percentage$userid";
|
|
|
785 |
$gradeitemdata['percentageformatted'] = $data['percentage']['content'];
|
|
|
786 |
}
|
|
|
787 |
|
|
|
788 |
// Lettergrade.
|
|
|
789 |
if ($this->showlettergrade) {
|
|
|
790 |
if ($gradegrade->grade_item->needsupdate) {
|
|
|
791 |
$data['lettergrade']['class'] = $class.' gradingerror';
|
|
|
792 |
$data['lettergrade']['content'] = get_string('error');
|
|
|
793 |
} else if ($gradegrade->is_hidden()) {
|
|
|
794 |
$data['lettergrade']['class'] = $class.' dimmed_text';
|
|
|
795 |
if (!$this->canviewhidden) {
|
|
|
796 |
$data['lettergrade']['content'] = '-';
|
|
|
797 |
} else {
|
|
|
798 |
$data['lettergrade']['content'] = grade_format_gradevalue(
|
|
|
799 |
$gradeval,
|
|
|
800 |
$gradegrade->grade_item,
|
|
|
801 |
true,
|
|
|
802 |
GRADE_DISPLAY_TYPE_LETTER
|
|
|
803 |
);
|
|
|
804 |
}
|
|
|
805 |
} else {
|
|
|
806 |
$data['lettergrade']['class'] = $class;
|
|
|
807 |
$data['lettergrade']['content'] = grade_format_gradevalue(
|
|
|
808 |
$gradeval,
|
|
|
809 |
$gradegrade->grade_item,
|
|
|
810 |
true,
|
|
|
811 |
GRADE_DISPLAY_TYPE_LETTER
|
|
|
812 |
);
|
|
|
813 |
}
|
|
|
814 |
$data['lettergrade']['headers'] = "$headercat $headerrow lettergrade$userid";
|
|
|
815 |
$gradeitemdata['lettergradeformatted'] = $data['lettergrade']['content'];
|
|
|
816 |
}
|
|
|
817 |
|
|
|
818 |
// Rank.
|
|
|
819 |
if ($this->showrank) {
|
|
|
820 |
$gradeitemdata['rank'] = 0;
|
|
|
821 |
if ($gradegrade->grade_item->needsupdate) {
|
|
|
822 |
$data['rank']['class'] = $class.' gradingerror';
|
|
|
823 |
$data['rank']['content'] = get_string('error');
|
|
|
824 |
} else if ($gradegrade->is_hidden()) {
|
|
|
825 |
$data['rank']['class'] = $class.' dimmed_text';
|
|
|
826 |
$data['rank']['content'] = '-';
|
|
|
827 |
} else if (is_null($gradeval)) {
|
|
|
828 |
// No grade, o rank.
|
|
|
829 |
$data['rank']['class'] = $class;
|
|
|
830 |
$data['rank']['content'] = '-';
|
|
|
831 |
|
|
|
832 |
} else {
|
|
|
833 |
// Find the number of users with a higher grade.
|
|
|
834 |
$sql = "SELECT COUNT(DISTINCT(userid))
|
|
|
835 |
FROM {grade_grades}
|
|
|
836 |
WHERE finalgrade > ?
|
|
|
837 |
AND itemid = ?
|
|
|
838 |
AND hidden = 0";
|
|
|
839 |
$rank = $DB->count_records_sql($sql, [$gradegrade->finalgrade, $gradegrade->grade_item->id]) + 1;
|
|
|
840 |
|
|
|
841 |
$data['rank']['class'] = $class;
|
|
|
842 |
$numusers = $this->get_numusers(false);
|
|
|
843 |
$data['rank']['content'] = "$rank/$numusers"; // Total course users.
|
|
|
844 |
|
|
|
845 |
$gradeitemdata['rank'] = $rank;
|
|
|
846 |
$gradeitemdata['numusers'] = $numusers;
|
|
|
847 |
}
|
|
|
848 |
$data['rank']['headers'] = "$headercat $headerrow rank$userid";
|
|
|
849 |
}
|
|
|
850 |
|
|
|
851 |
// Average.
|
|
|
852 |
if ($this->showaverage) {
|
|
|
853 |
$data['average']['class'] = $class;
|
|
|
854 |
$cache = \cache::make_from_params(\cache_store::MODE_REQUEST, 'gradereport_user', 'averages');
|
|
|
855 |
$avg = $cache->get(get_class($this));
|
|
|
856 |
|
|
|
857 |
$data['average']['content'] = $avg[$eid]->text;;
|
|
|
858 |
$gradeitemdata['averageformatted'] = $avg[$eid]->text;;
|
|
|
859 |
$data['average']['headers'] = "$headercat $headerrow average$userid";
|
|
|
860 |
}
|
|
|
861 |
|
|
|
862 |
// Feedback.
|
|
|
863 |
if ($this->showfeedback) {
|
|
|
864 |
$gradeitemdata['feedback'] = '';
|
|
|
865 |
$gradeitemdata['feedbackformat'] = $gradegrade->feedbackformat;
|
|
|
866 |
|
|
|
867 |
if ($gradegrade->feedback) {
|
|
|
868 |
$gradegrade->feedback = file_rewrite_pluginfile_urls(
|
|
|
869 |
$gradegrade->feedback,
|
|
|
870 |
'pluginfile.php',
|
|
|
871 |
$gradegrade->get_context()->id,
|
|
|
872 |
GRADE_FILE_COMPONENT,
|
|
|
873 |
GRADE_FEEDBACK_FILEAREA,
|
|
|
874 |
$gradegrade->id
|
|
|
875 |
);
|
|
|
876 |
}
|
|
|
877 |
|
|
|
878 |
$data['feedback']['class'] = $classfeedback.' feedbacktext';
|
|
|
879 |
if (empty($gradegrade->feedback) || (!$this->canviewhidden && $gradegrade->is_hidden())) {
|
|
|
880 |
$data['feedback']['content'] = ' ';
|
|
|
881 |
} else {
|
|
|
882 |
$data['feedback']['content'] = format_text($gradegrade->feedback, $gradegrade->feedbackformat,
|
|
|
883 |
['context' => $gradegrade->get_context()]);
|
|
|
884 |
$gradeitemdata['feedback'] = $gradegrade->feedback;
|
|
|
885 |
}
|
|
|
886 |
$data['feedback']['headers'] = "$headercat $headerrow feedback$userid";
|
|
|
887 |
}
|
|
|
888 |
// Contribution to the course total column.
|
|
|
889 |
if ($this->showcontributiontocoursetotal) {
|
|
|
890 |
$data['contributiontocoursetotal']['class'] = $class;
|
|
|
891 |
$data['contributiontocoursetotal']['content'] = '-';
|
|
|
892 |
$data['contributiontocoursetotal']['headers'] = "$headercat $headerrow contributiontocoursetotal$userid";
|
|
|
893 |
|
|
|
894 |
}
|
|
|
895 |
$this->gradeitemsdata[] = $gradeitemdata;
|
|
|
896 |
}
|
|
|
897 |
|
|
|
898 |
$parent = $gradeobject->load_parent_category();
|
|
|
899 |
if ($gradeobject->is_category_item()) {
|
|
|
900 |
$parent = $parent->load_parent_category();
|
|
|
901 |
}
|
|
|
902 |
|
|
|
903 |
// We collect the aggregation hints whether they are hidden or not.
|
|
|
904 |
if ($this->showcontributiontocoursetotal) {
|
|
|
905 |
$hint['grademax'] = $gradegrade->grade_item->grademax;
|
|
|
906 |
$hint['grademin'] = $gradegrade->grade_item->grademin;
|
|
|
907 |
$hint['grade'] = $gradeval;
|
|
|
908 |
$hint['parent'] = $parent->load_grade_item()->id;
|
|
|
909 |
$this->aggregationhints[$gradegrade->itemid] = $hint;
|
|
|
910 |
}
|
|
|
911 |
// Get the IDs of all parent categories of this grading item.
|
|
|
912 |
$data['parentcategories'] = array_filter(explode('/', $gradeobject->parent_category->path));
|
|
|
913 |
}
|
|
|
914 |
|
|
|
915 |
// Category.
|
|
|
916 |
if ($type == 'category') {
|
|
|
917 |
// Determine directionality so that icons can be modified to suit language.
|
|
|
918 |
$arrow = right_to_left() ? 'left' : 'right';
|
|
|
919 |
// Alter style based on whether aggregation is first or last.
|
|
|
920 |
if ($this->switch) {
|
|
|
921 |
$data['itemname']['class'] = $class . ' ' . "d$depth b1b b1t category";
|
|
|
922 |
} else {
|
|
|
923 |
$data['itemname']['class'] = $class . ' ' . "d$depth b2t category";
|
|
|
924 |
}
|
|
|
925 |
$data['itemname']['colspan'] = ($this->maxdepth - $depth + count($this->tablecolumns));
|
|
|
926 |
$data['itemname']['content'] = $OUTPUT->render_from_template('gradereport_user/user_report_category_content',
|
|
|
927 |
['categoryid' => $gradeobject->id, 'categoryname' => $fullname, 'arrow' => $arrow]);
|
|
|
928 |
$data['itemname']['id'] = "cat_{$gradeobject->id}_{$this->user->id}";
|
|
|
929 |
// Get the IDs of all parent categories of this grade category.
|
|
|
930 |
$data['parentcategories'] = array_diff(array_filter(explode('/', $gradeobject->path)), [$gradeobject->id]);
|
|
|
931 |
|
|
|
932 |
$rowspandata['leader']['class'] = $class . " d$depth b1t b2b b1l";
|
|
|
933 |
$rowspandata['leader']['rowspan'] = $element['rowspan'];
|
|
|
934 |
$rowspandata['parentcategories'] = array_filter(explode('/', $gradeobject->path));
|
|
|
935 |
$rowspandata['spacer'] = true;
|
|
|
936 |
}
|
|
|
937 |
|
|
|
938 |
// Add this row to the overall system.
|
|
|
939 |
foreach ($data as $key => $celldata) {
|
|
|
940 |
if (isset($celldata['class'])) {
|
|
|
941 |
$data[$key]['class'] .= ' column-' . $key;
|
|
|
942 |
}
|
|
|
943 |
}
|
|
|
944 |
|
|
|
945 |
$this->tabledata[] = $data;
|
|
|
946 |
|
|
|
947 |
if (!empty($rowspandata)) {
|
|
|
948 |
$this->tabledata[] = $rowspandata;
|
|
|
949 |
}
|
|
|
950 |
|
|
|
951 |
// Recursively iterate through all child elements.
|
|
|
952 |
if (isset($element['children'])) {
|
|
|
953 |
foreach ($element['children'] as $key => $child) {
|
|
|
954 |
$this->fill_table_recursive($element['children'][$key]);
|
|
|
955 |
}
|
|
|
956 |
}
|
|
|
957 |
|
|
|
958 |
// Check we are showing this column, and we are looking at the root of the table.
|
|
|
959 |
// This should be the very last thing this fill_table_recursive function does.
|
|
|
960 |
if ($this->showcontributiontocoursetotal && ($type == 'category' && $depth == 1)) {
|
|
|
961 |
// We should have collected all the hints by now - walk the tree again and build the contributions column.
|
|
|
962 |
$this->fill_contributions_column($element);
|
|
|
963 |
}
|
|
|
964 |
}
|
|
|
965 |
|
|
|
966 |
/**
|
|
|
967 |
* This function is called after the table has been built and the aggregationhints
|
|
|
968 |
* have been collected. We need this info to walk up the list of parents of each
|
|
|
969 |
* grade_item.
|
|
|
970 |
*
|
|
|
971 |
* @param array $element - An array containing the table data for the current row.
|
|
|
972 |
*/
|
|
|
973 |
public function fill_contributions_column(array $element) {
|
|
|
974 |
|
|
|
975 |
// Recursively iterate through all child elements.
|
|
|
976 |
if (isset($element['children'])) {
|
|
|
977 |
foreach ($element['children'] as $key => $child) {
|
|
|
978 |
$this->fill_contributions_column($element['children'][$key]);
|
|
|
979 |
}
|
|
|
980 |
} else if ($element['type'] == 'item') {
|
|
|
981 |
// This is a grade item (We don't do this for categories or we would double count).
|
|
|
982 |
$gradeobject = $element['object'];
|
|
|
983 |
$itemid = $gradeobject->id;
|
|
|
984 |
|
|
|
985 |
// Ignore anything with no hint - e.g. a hidden row.
|
|
|
986 |
if (isset($this->aggregationhints[$itemid])) {
|
|
|
987 |
|
|
|
988 |
// Normalise the gradeval.
|
|
|
989 |
$gradecat = $gradeobject->load_parent_category();
|
|
|
990 |
if ($gradecat->aggregation == GRADE_AGGREGATE_SUM) {
|
|
|
991 |
// Natural aggregation/Sum of grades does not consider the mingrade, cannot traditionnally normalise it.
|
|
|
992 |
$graderange = $this->aggregationhints[$itemid]['grademax'];
|
|
|
993 |
|
|
|
994 |
if ($graderange != 0) {
|
|
|
995 |
$gradeval = $this->aggregationhints[$itemid]['grade'] / $graderange;
|
|
|
996 |
} else {
|
|
|
997 |
$gradeval = 0;
|
|
|
998 |
}
|
|
|
999 |
} else {
|
|
|
1000 |
$gradeval = grade_grade::standardise_score(
|
|
|
1001 |
$this->aggregationhints[$itemid]['grade'],
|
|
|
1002 |
$this->aggregationhints[$itemid]['grademin'],
|
|
|
1003 |
$this->aggregationhints[$itemid]['grademax'],
|
|
|
1004 |
0,
|
|
|
1005 |
1
|
|
|
1006 |
);
|
|
|
1007 |
}
|
|
|
1008 |
|
|
|
1009 |
// Multiply the normalised value by the weight
|
|
|
1010 |
// of all the categories higher in the tree.
|
|
|
1011 |
$parent = null;
|
|
|
1012 |
do {
|
|
|
1013 |
if (!is_null($this->aggregationhints[$itemid]['weight'])) {
|
|
|
1014 |
$gradeval *= $this->aggregationhints[$itemid]['weight'];
|
|
|
1015 |
} else if (empty($parent)) {
|
|
|
1016 |
// If we are in the first loop, and the weight is null, then we cannot calculate the contribution.
|
|
|
1017 |
$gradeval = null;
|
|
|
1018 |
break;
|
|
|
1019 |
}
|
|
|
1020 |
|
|
|
1021 |
// The second part of this if is to prevent infinite loops
|
|
|
1022 |
// in case of crazy data.
|
|
|
1023 |
if (isset($this->aggregationhints[$itemid]['parent']) &&
|
|
|
1024 |
$this->aggregationhints[$itemid]['parent'] != $itemid) {
|
|
|
1025 |
$parent = $this->aggregationhints[$itemid]['parent'];
|
|
|
1026 |
$itemid = $parent;
|
|
|
1027 |
} else {
|
|
|
1028 |
// We are at the top of the tree.
|
|
|
1029 |
$parent = false;
|
|
|
1030 |
}
|
|
|
1031 |
} while ($parent);
|
|
|
1032 |
|
|
|
1033 |
// Finally multiply by the course grademax.
|
|
|
1034 |
if (!is_null($gradeval)) {
|
|
|
1035 |
// Convert to percent.
|
|
|
1036 |
$gradeval *= 100;
|
|
|
1037 |
}
|
|
|
1038 |
|
|
|
1039 |
// Now we need to loop through the "built" table data and update the
|
|
|
1040 |
// contributions column for the current row.
|
|
|
1041 |
$headerrow = "row_{$gradeobject->id}_{$this->user->id}";
|
|
|
1042 |
foreach ($this->tabledata as $key => $row) {
|
|
|
1043 |
if (isset($row['itemname']) && ($row['itemname']['id'] == $headerrow)) {
|
|
|
1044 |
// Found it - update the column.
|
|
|
1045 |
$content = '-';
|
|
|
1046 |
if (!is_null($gradeval)) {
|
|
|
1047 |
$decimals = $gradeobject->get_decimals();
|
|
|
1048 |
$content = format_float($gradeval, $decimals, true) . ' %';
|
|
|
1049 |
}
|
|
|
1050 |
$this->tabledata[$key]['contributiontocoursetotal']['content'] = $content;
|
|
|
1051 |
break;
|
|
|
1052 |
}
|
|
|
1053 |
}
|
|
|
1054 |
}
|
|
|
1055 |
}
|
|
|
1056 |
}
|
|
|
1057 |
|
|
|
1058 |
/**
|
|
|
1059 |
* Prints or returns the HTML from the flexitable.
|
|
|
1060 |
*
|
|
|
1061 |
* @param bool $return Whether or not to return the data instead of printing it directly.
|
|
|
1062 |
* @return string|void
|
|
|
1063 |
*/
|
|
|
1064 |
public function print_table(bool $return = false) {
|
|
|
1065 |
global $PAGE;
|
|
|
1066 |
|
|
|
1067 |
$table = new \html_table();
|
|
|
1068 |
$table->attributes = [
|
|
|
1069 |
'summary' => s(get_string('tablesummary', 'gradereport_user')),
|
|
|
1070 |
'class' => 'generaltable boxaligncenter user-grade',
|
|
|
1071 |
];
|
|
|
1072 |
|
|
|
1073 |
// Set the table headings.
|
|
|
1074 |
$userid = $this->user->id;
|
|
|
1075 |
foreach ($this->tableheaders as $index => $heading) {
|
|
|
1076 |
$headingcell = new \html_table_cell($heading);
|
|
|
1077 |
$headingcell->attributes['id'] = $this->tablecolumns[$index] . $userid;
|
|
|
1078 |
$headingcell->attributes['class'] = "header column-{$this->tablecolumns[$index]}";
|
|
|
1079 |
if ($index == 0) {
|
|
|
1080 |
$headingcell->colspan = $this->maxdepth;
|
|
|
1081 |
}
|
|
|
1082 |
$table->head[] = $headingcell;
|
|
|
1083 |
}
|
|
|
1084 |
|
|
|
1085 |
// Set the table body data.
|
|
|
1086 |
foreach ($this->tabledata as $rowdata) {
|
|
|
1087 |
$rowcells = [];
|
|
|
1088 |
// Set a rowspan cell, if applicable.
|
|
|
1089 |
if (isset($rowdata['leader'])) {
|
|
|
1090 |
$rowspancell = new \html_table_cell('');
|
|
|
1091 |
$rowspancell->attributes['class'] = $rowdata['leader']['class'];
|
|
|
1092 |
$rowspancell->rowspan = $rowdata['leader']['rowspan'];
|
|
|
1093 |
$rowcells[] = $rowspancell;
|
|
|
1094 |
}
|
|
|
1095 |
|
|
|
1096 |
// Set the row cells.
|
|
|
1097 |
foreach ($this->tablecolumns as $tablecolumn) {
|
|
|
1098 |
$content = $rowdata[$tablecolumn]['content'] ?? null;
|
|
|
1099 |
|
|
|
1100 |
if (!is_null($content)) {
|
|
|
1101 |
$rowcell = new \html_table_cell($content);
|
|
|
1102 |
|
|
|
1103 |
// Grade item names and cateogry names are referenced in the `headers` attribute of table cells.
|
|
|
1104 |
// These table cells should be set to <th> tags.
|
|
|
1105 |
if ($tablecolumn === 'itemname') {
|
|
|
1106 |
$rowcell->header = true;
|
|
|
1107 |
}
|
|
|
1108 |
|
|
|
1109 |
if (isset($rowdata[$tablecolumn]['class'])) {
|
|
|
1110 |
$rowcell->attributes['class'] = $rowdata[$tablecolumn]['class'];
|
|
|
1111 |
}
|
|
|
1112 |
if (isset($rowdata[$tablecolumn]['colspan'])) {
|
|
|
1113 |
$rowcell->colspan = $rowdata[$tablecolumn]['colspan'];
|
|
|
1114 |
}
|
|
|
1115 |
if (isset($rowdata[$tablecolumn]['id'])) {
|
|
|
1116 |
$rowcell->id = $rowdata[$tablecolumn]['id'];
|
|
|
1117 |
}
|
|
|
1118 |
if (isset($rowdata[$tablecolumn]['headers'])) {
|
|
|
1119 |
$rowcell->attributes['headers'] = $rowdata[$tablecolumn]['headers'];
|
|
|
1120 |
}
|
|
|
1121 |
$rowcells[] = $rowcell;
|
|
|
1122 |
}
|
|
|
1123 |
}
|
|
|
1124 |
|
|
|
1125 |
$tablerow = new \html_table_row($rowcells);
|
|
|
1126 |
// Generate classes which will be attributed to the current row and will be used to identify all parent
|
|
|
1127 |
// categories of this grading item or a category (e.g. 'cat_2 cat_5'). These classes are utilized by the
|
|
|
1128 |
// category toggle (expand/collapse) functionality.
|
|
|
1129 |
$classes = implode(" ", array_map(function($parentcategoryid) {
|
|
|
1130 |
return "cat_{$parentcategoryid}";
|
|
|
1131 |
}, $rowdata['parentcategories']));
|
|
|
1132 |
|
|
|
1133 |
$classes .= isset($rowdata['spacer']) && $rowdata['spacer'] ? ' spacer' : '';
|
|
|
1134 |
|
|
|
1135 |
$tablerow->attributes = ['class' => $classes, 'data-hidden' => 'false'];
|
|
|
1136 |
$table->data[] = $tablerow;
|
|
|
1137 |
}
|
|
|
1138 |
|
|
|
1139 |
$userreporttable = \html_writer::table($table);
|
|
|
1140 |
$PAGE->requires->js_call_amd('gradereport_user/gradecategorytoggle', 'init', ["user-report-{$this->user->id}"]);
|
|
|
1141 |
|
|
|
1142 |
if ($return) {
|
|
|
1143 |
return \html_writer::div($userreporttable, 'user-report-container', ['id' => "user-report-{$this->user->id}"]);
|
|
|
1144 |
}
|
|
|
1145 |
|
|
|
1146 |
echo \html_writer::div($userreporttable, 'user-report-container', ['id' => "user-report-{$this->user->id}"]);
|
|
|
1147 |
}
|
|
|
1148 |
|
|
|
1149 |
/**
|
|
|
1150 |
* Processes the data sent by the form (grades and feedbacks).
|
|
|
1151 |
*
|
|
|
1152 |
* @param array $data Take in some data to provide to the base function.
|
|
|
1153 |
* @return void Success or Failure (array of errors).
|
|
|
1154 |
*/
|
|
|
1155 |
public function process_data($data): void {
|
|
|
1156 |
}
|
|
|
1157 |
|
|
|
1158 |
/**
|
|
|
1159 |
* Stub function.
|
|
|
1160 |
*
|
|
|
1161 |
* @param string $target
|
|
|
1162 |
* @param string $action
|
|
|
1163 |
* @return void
|
|
|
1164 |
*/
|
|
|
1165 |
public function process_action($target, $action): void {
|
|
|
1166 |
}
|
|
|
1167 |
|
|
|
1168 |
/**
|
|
|
1169 |
* Build the html for the zero state of the user report.
|
|
|
1170 |
* @return string HTML to display
|
|
|
1171 |
*/
|
|
|
1172 |
public function output_report_zerostate(): string {
|
|
|
1173 |
global $OUTPUT;
|
|
|
1174 |
|
|
|
1175 |
$context = [
|
|
|
1176 |
'imglink' => $OUTPUT->image_url('zero_state', 'gradereport_user'),
|
|
|
1177 |
];
|
|
|
1178 |
return $OUTPUT->render_from_template('gradereport_user/zero_state', $context);
|
|
|
1179 |
}
|
|
|
1180 |
|
|
|
1181 |
/**
|
|
|
1182 |
* Trigger the grade_report_viewed event
|
|
|
1183 |
*
|
|
|
1184 |
* @since Moodle 2.9
|
|
|
1185 |
*/
|
|
|
1186 |
public function viewed() {
|
|
|
1187 |
$event = \gradereport_user\event\grade_report_viewed::create(
|
|
|
1188 |
[
|
|
|
1189 |
'context' => $this->context,
|
|
|
1190 |
'courseid' => $this->courseid,
|
|
|
1191 |
'relateduserid' => $this->user->id,
|
|
|
1192 |
]
|
|
|
1193 |
);
|
|
|
1194 |
$event->trigger();
|
|
|
1195 |
}
|
|
|
1196 |
}
|