weekly back-to-dev release 5.0dev
[moodle.git] / grade / report / singleview / classes / local / screen / user.php
blobb6a3c9e173eea67ea4b7a00a8876ee63d8d812db
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.
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/>.
17 /**
18 * The user screen.
20 * @package gradereport_singleview
21 * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 namespace gradereport_singleview\local\screen;
27 use grade_seq;
28 use gradereport_singleview;
29 use moodle_url;
30 use pix_icon;
31 use html_writer;
32 use gradereport_singleview\local\ui\range;
33 use gradereport_singleview\local\ui\bulk_insert;
34 use grade_item;
35 use grade_grade;
36 use stdClass;
38 defined('MOODLE_INTERNAL') || die;
40 /**
41 * The user screen.
43 * @package gradereport_singleview
44 * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
45 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47 class user extends tablelike implements selectable_items {
49 /** @var array $categories A cache for grade_item categories */
50 private $categories = [];
52 /** @var int $requirespaging Do we have more items than the paging limit? */
53 private $requirespaging = true;
55 /** @var array get a valid user. */
56 public $item = [];
58 /**
59 * Get the label for the select box that chooses items for this page.
60 * @return string
62 public function select_label(): string {
63 return get_string('selectgrade', 'gradereport_singleview');
66 /**
67 * Get the description for the screen.
69 * @return string
71 public function description(): string {
72 return get_string('gradeitems', 'grades');
75 /**
76 * Convert the list of items to a list of options.
78 * @return array
80 public function options(): array {
81 $result = [];
82 foreach ($this->items as $itemid => $item) {
83 $result[$itemid] = $item->get_name();
85 return $result;
88 /**
89 * Get the type of items on this screen.
91 * @return string
93 public function item_type(): string {
94 return 'grade';
97 /**
98 * Init the screen
100 * @param bool $selfitemisempty Have we selected an item yet?
102 public function init($selfitemisempty = false) {
104 if (!$selfitemisempty) {
105 $validusers = \grade_report::get_gradable_users($this->courseid, $this->groupid);
106 if (!isset($validusers[$this->itemid])) {
107 // If the passed user id is not valid, show the first user from the list instead.
108 $this->item = reset($validusers);
109 $this->itemid = $this->item->id;
110 } else {
111 $this->item = $validusers[$this->itemid];
115 $seq = new grade_seq($this->courseid, true);
117 $this->items = [];
118 foreach ($seq->items as $itemid => $item) {
119 if (grade::filter($item)) {
120 $this->items[$itemid] = $item;
124 // If we change perpage on pagination we might end up with a page that doesn't exist.
125 if ($this->perpage) {
126 $numpages = intval(count($this->items) / $this->perpage) + 1;
127 if ($numpages <= $this->page) {
128 $this->page = 0;
130 } else {
131 $this->page = 0;
134 $this->requirespaging = count($this->items) > $this->perpage;
136 $this->setup_structure();
138 $this->definition = [
139 'finalgrade', 'feedback', 'override', 'exclude'
141 $this->set_headers($this->original_headers());
145 * Get the list of headers for the table.
147 * @return array List of headers
149 public function original_headers(): array {
150 return [
151 get_string('assessmentname', 'gradereport_singleview'),
152 '', // For filter icon.
153 get_string('gradecategory', 'grades'),
154 get_string('gradenoun'),
155 get_string('range', 'grades'),
156 get_string('feedback', 'grades'),
157 get_string('override', 'gradereport_singleview'),
158 get_string('exclude', 'gradereport_singleview'),
163 * Format each row of the table.
165 * @param grade_item $item
166 * @return array
168 public function format_line($item): array {
169 global $OUTPUT;
171 $grade = $this->fetch_grade_or_default($item, $this->item->id);
172 $gradestatus = '';
174 $context = [
175 'hidden' => $grade->is_hidden(),
176 'locked' => $grade->is_locked(),
179 if (in_array(true, $context)) {
180 $context['classes'] = 'gradestatus';
181 $gradestatus = $OUTPUT->render_from_template('core_grades/status_icons', $context);
184 // Create a fake gradetreeitem so we can call get_element_header().
185 // The type logic below is from grade_category->_get_children_recursion().
186 $gradetreeitem = [];
188 $type = in_array($item->itemtype, ['course', 'category']) ? "{$item->itemtype}item" : 'item';
189 $gradetreeitem['type'] = $type;
190 $gradetreeitem['object'] = $item;
191 $gradetreeitem['userid'] = $this->item->id;
193 $itemname = \grade_helper::get_element_header($gradetreeitem, true, false, false, false, true);
194 $grade->label = $item->get_name();
196 $formatteddefinition = $this->format_definition($grade);
198 $itemicon = html_writer::div($this->format_icon($item), 'me-1');
199 $itemtype = \html_writer::span(\grade_helper::get_element_type_string($gradetreeitem),
200 'd-block text-uppercase small dimmed_text');
202 $itemtitle = html_writer::div($itemname, 'rowtitle');
203 $itemcontent = html_writer::div($itemtype . $itemtitle);
205 $line = [
206 html_writer::div($itemicon . $itemcontent, "{$type} d-flex align-items-center"),
207 $this->get_item_action_menu($item),
208 $this->category($item),
209 $formatteddefinition['finalgrade'] . $gradestatus,
210 new range($item),
211 $formatteddefinition['feedback'],
212 $formatteddefinition['override'],
213 $formatteddefinition['exclude'],
215 $lineclasses = [
216 'gradeitem',
217 'action',
218 'category',
219 'grade',
220 'range',
223 $outputline = [];
224 $i = 0;
225 foreach ($line as $key => $value) {
226 $cell = new \html_table_cell($value);
227 if ($isheader = $i == 0) {
228 $cell->header = $isheader;
229 $cell->scope = "row";
231 if (array_key_exists($key, $lineclasses)) {
232 $cell->attributes['class'] = $lineclasses[$key];
234 $outputline[] = $cell;
235 $i++;
238 return $outputline;
242 * Helper to get the icon for an item.
244 * @param grade_item $item
245 * @return string
247 private function format_icon($item): string {
248 $element = ['type' => 'item', 'object' => $item];
249 return \grade_helper::get_element_icon($element);
253 * Return the action menu HTML for the grade item.
255 * @param grade_item $item
256 * @return mixed
258 private function get_item_action_menu(grade_item $item) {
259 global $OUTPUT;
261 $menuitems = [];
262 $url = new moodle_url($this->format_link('grade', $item->id));
263 $title = get_string('showallgrades', 'core_grades');
264 $menuitems[] = new \action_menu_link_secondary($url, null, $title);
265 $menu = new \action_menu($menuitems);
266 $icon = $OUTPUT->pix_icon('i/moremenu', get_string('actions'));
267 $extraclasses = 'btn btn-link btn-icon icon-size-3 d-flex align-items-center justify-content-center';
268 $menu->set_menu_trigger($icon, $extraclasses);
269 $menu->set_menu_left();
270 $menu->set_boundary('window');
272 return $OUTPUT->render($menu);
276 * Helper to get the category for an item.
278 * @param grade_item $item
279 * @return string
281 private function category(grade_item $item): string {
282 global $DB;
284 if (empty($item->categoryid)) {
286 if ($item->itemtype == 'course') {
287 return $this->course->fullname;
290 $params = ['id' => $item->iteminstance];
291 $elem = $DB->get_record('grade_categories', $params);
293 return $elem->fullname;
296 if (!isset($this->categories[$item->categoryid])) {
297 $category = $item->get_parent_category();
299 $this->categories[$category->id] = $category;
302 return $this->categories[$item->categoryid]->get_name();
306 * Get the heading for the page.
308 * @return string
310 public function heading(): string {
311 global $PAGE;
312 $headinglangstring = $PAGE->user_is_editing() ? 'gradeuseredit' : 'gradeuser';
313 return get_string($headinglangstring, 'gradereport_singleview', fullname($this->item));
317 * Get the summary for this table.
319 * @return string
321 public function summary(): string {
322 return get_string('summaryuser', 'gradereport_singleview');
326 * Default pager
328 * @return string
330 public function pager(): string {
331 global $OUTPUT;
333 if (!$this->supports_paging()) {
334 return '';
337 return $OUTPUT->paging_bar(
338 count($this->items), $this->page, $this->perpage,
339 new moodle_url('/grade/report/singleview/index.php', [
340 'perpage' => $this->perpage,
341 'id' => $this->courseid,
342 'group' => $this->groupid,
343 'itemid' => $this->itemid,
344 'item' => 'user'
350 * Does this page require paging?
352 * @return bool
354 public function supports_paging(): bool {
355 return $this->requirespaging;
360 * Process the data from the form.
362 * @param array $data
363 * @return stdClass of warnings
365 public function process($data): stdClass {
366 $bulk = new bulk_insert($this->item);
367 // Bulk insert messages the data to be passed in
368 // ie: for all grades of empty grades apply the specified value.
369 if ($bulk->is_applied($data)) {
370 $filter = $bulk->get_type($data);
371 $insertvalue = $bulk->get_insert_value($data);
373 $userid = $this->item->id;
374 foreach ($this->items as $gradeitemid => $gradeitem) {
375 $null = $gradeitem->gradetype == GRADE_TYPE_SCALE ? -1 : '';
376 $field = "finalgrade_{$gradeitem->id}_{$this->itemid}";
377 if (isset($data->$field)) {
378 continue;
381 $oldfinalgradefield = "oldfinalgrade_{$gradeitem->id}_{$this->itemid}";
382 // Bulk grade changes for all grades need to be processed and shouldn't be skipped if they had a previous grade.
383 if ($gradeitem->is_course_item() || ($filter != 'all' && !empty($data->$oldfinalgradefield))) {
384 if ($gradeitem->is_course_item()) {
385 // The course total should not be overridden.
386 unset($data->$field);
387 unset($data->oldfinalgradefield);
388 $oldoverride = "oldoverride_{$gradeitem->id}_{$this->itemid}";
389 unset($data->$oldoverride);
390 $oldfeedback = "oldfeedback_{$gradeitem->id}_{$this->itemid}";
391 unset($data->$oldfeedback);
393 continue;
395 $grade = grade_grade::fetch([
396 'itemid' => $gradeitemid,
397 'userid' => $userid
400 $data->$field = empty($grade) ? $null : $grade->finalgrade;
401 $data->{"old$field"} = $data->$field;
404 foreach ($data as $varname => $value) {
405 if (preg_match('/^oldoverride_(\d+)_(\d+)/', $varname, $matches)) {
406 // If we've selected overriding all grades.
407 if ($filter == 'all') {
408 $override = "override_{$matches[1]}_{$matches[2]}";
409 $data->$override = '1';
412 if (!preg_match('/^finalgrade_(\d+)_(\d+)/', $varname, $matches)) {
413 continue;
416 $gradeitem = grade_item::fetch([
417 'courseid' => $this->courseid,
418 'id' => $matches[1],
421 $isscale = ($gradeitem->gradetype == GRADE_TYPE_SCALE);
423 $empties = (trim($value ?? '') === '' || ($isscale && $value == -1));
425 if ($filter == 'all' || $empties) {
426 $data->$varname = ($isscale && empty($insertvalue)) ? -1 : $insertvalue;
430 return parent::process($data);