MDL-64161 gradereport_singleview: Single updates reported correctly.
[moodle.git] / grade / report / singleview / classes / local / screen / screen.php
CommitLineData
57fac09a
DW
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/**
18 * Abstract class used as a base for the 3 screens.
19 *
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
23 */
24
25namespace gradereport_singleview\local\screen;
26
27use context_course;
28use moodle_url;
29use html_writer;
30use grade_structure;
31use grade_grade;
32use grade_item;
33use stdClass;
34
35defined('MOODLE_INTERNAL') || die;
36
37/**
38 * Abstract class used as a base for the 3 screens.
39 *
40 * @package gradereport_singleview
41 * @copyright 2014 Moodle Pty Ltd (http://moodle.com)
42 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 */
44abstract class screen {
45
46 /** @var int $courseid The id of the course */
47 protected $courseid;
48
49 /** @var int $itemid Either a user id or a grade_item id */
50 protected $itemid;
51
52 /** @var int $groupid The currently set groupid (if set) */
53 protected $groupid;
54
55 /** @var course_context $context The course context */
56 protected $context;
57
58 /** @var int $page The page number */
59 protected $page;
60
61 /** @var int $perpage Results per page */
62 protected $perpage;
63
64 /** @var array $items List of items on the page, they could be users or grade_items */
65 protected $items;
66
c6dd683c
DS
67 /** @var array $validperpage List of allowed values for 'perpage' setting */
68 protected static $validperpage = [20, 50, 100, 200, 400, 1000, 5000];
69
57fac09a
DW
70 /**
71 * Constructor
72 *
73 * @param int $courseid The course id
74 * @param int $itemid The item id
75 * @param int $groupid The group id
76 */
77 public function __construct($courseid, $itemid, $groupid = null) {
78 global $DB;
79
80 $this->courseid = $courseid;
81 $this->itemid = $itemid;
82 $this->groupid = $groupid;
83
84 $this->context = context_course::instance($this->courseid);
85 $this->course = $DB->get_record('course', array('id' => $courseid));
86
87 $this->page = optional_param('page', 0, PARAM_INT);
c6dd683c
DS
88
89 $cache = \cache::make_from_params(\cache_store::MODE_SESSION, 'gradereport_singleview', 'perpage');
90 $perpage = optional_param('perpage', null, PARAM_INT);
91 if (!in_array($perpage, self::$validperpage)) {
92 // Get from cache.
93 $perpage = $cache->get(get_class($this));
94 } else {
95 // Save to cache.
96 $cache->set(get_class($this), $perpage);
97 }
98 if ($perpage) {
99 $this->perpage = $perpage;
100 } else {
57fac09a
DW
101 $this->perpage = 100;
102 }
103
104 $this->init(empty($itemid));
105 }
106
107 /**
108 * Cache the grade_structure class
109 */
110 public function setup_structure() {
111 $this->structure = new grade_structure();
112 $this->structure->modinfo = get_fast_modinfo($this->course);
113 }
114
115 /**
116 * Create a nice link from a thing (user or grade_item).
117 *
118 * @param string $screen
119 * @param int $itemid
120 * @param bool $display Should we wrap this in an anchor ?
121 * @return string The link
122 */
123 public function format_link($screen, $itemid, $display = null) {
124 $url = new moodle_url('/grade/report/singleview/index.php', array(
125 'id' => $this->courseid,
126 'item' => $screen,
127 'itemid' => $itemid,
128 'group' => $this->groupid,
129 ));
130
131 if ($display) {
132 return html_writer::link($url, $display);
133 } else {
134 return $url;
135 }
136 }
137
138 /**
139 * Get the grade_grade
140 *
141 * @param grade_item $item The grade_item
142 * @param int $userid The user id
143 * @return grade_grade
144 */
145 public function fetch_grade_or_default($item, $userid) {
146 $grade = grade_grade::fetch(array(
147 'itemid' => $item->id, 'userid' => $userid
148 ));
149
150 if (!$grade) {
151 $default = new stdClass;
152
153 $default->userid = $userid;
154 $default->itemid = $item->id;
155 $default->feedback = '';
156
157 $grade = new grade_grade($default, false);
158 }
159
160 $grade->grade_item = $item;
161
162 return $grade;
163 }
164
165 /**
166 * Make the HTML element that toggles all the checkboxes on or off.
167 *
168 * @param string $key A unique key for this control - inserted in the classes.
169 * @return string
170 */
171 public function make_toggle($key) {
172 $attrs = array('href' => '#');
173
dd01789b
JC
174 // Do proper lang strings for title attributes exist for the given key?
175 $strmanager = \get_string_manager();
176 $titleall = get_string('all');
177 $titlenone = get_string('none');
178 if ($strmanager->string_exists(strtolower($key) . 'all', 'gradereport_singleview')) {
179 $titleall = get_string(strtolower($key) . 'all', 'gradereport_singleview');
180 }
181 if ($strmanager->string_exists(strtolower($key) . 'none', 'gradereport_singleview')) {
182 $titlenone = get_string(strtolower($key) . 'none', 'gradereport_singleview');
183 }
184
57fac09a 185 $all = html_writer::tag('a', get_string('all'), $attrs + array(
dd01789b
JC
186 'class' => 'include all ' . $key,
187 'title' => $titleall
57fac09a
DW
188 ));
189
190 $none = html_writer::tag('a', get_string('none'), $attrs + array(
dd01789b
JC
191 'class' => 'include none ' . $key,
192 'title' => $titlenone
57fac09a
DW
193 ));
194
195 return html_writer::tag('span', "$all / $none", array(
196 'class' => 'inclusion_links'
197 ));
198 }
199
200 /**
201 * Make a toggle link with some text before it.
202 *
203 * @param string $key A unique key for this control - inserted in the classes.
204 * @return string
205 */
206 public function make_toggle_links($key) {
207 return get_string($key, 'gradereport_singleview') . ' ' .
208 $this->make_toggle($key);
209 }
210
211 /**
212 * Get the default heading for the screen.
213 *
214 * @return string
215 */
216 public function heading() {
2a6cdc51 217 return get_string('entrypage', 'gradereport_singleview');
57fac09a
DW
218 }
219
220 /**
221 * Override this to init the screen.
222 *
223 * @param boolean $selfitemisempty True if no item has been selected yet.
224 */
225 public abstract function init($selfitemisempty = false);
226
227 /**
228 * Get the type of items in the list.
229 *
230 * @return string
231 */
232 public abstract function item_type();
233
234 /**
235 * Get the entire screen as a string.
236 *
237 * @return string
238 */
239 public abstract function html();
240
241 /**
242 * Does this screen support paging?
243 *
244 * @return bool
245 */
246 public function supports_paging() {
247 return true;
248 }
249
250 /**
251 * Default pager
252 *
253 * @return string
254 */
255 public function pager() {
256 return '';
257 }
258
259 /**
260 * Initialise the js for this screen.
261 */
262 public function js() {
263 global $PAGE;
264
265 $module = array(
266 'name' => 'gradereport_singleview',
267 'fullpath' => '/grade/report/singleview/js/singleview.js',
268 'requires' => array('base', 'dom', 'event', 'event-simulate', 'io-base')
269 );
270
271 $PAGE->requires->js_init_call('M.gradereport_singleview.init', array(), false, $module);
272 }
273
274 /**
275 * Process the data from a form submission.
276 *
277 * @param array $data
278 * @return array of warnings
279 */
280 public function process($data) {
281 $warnings = array();
282
283 $fields = $this->definition();
284
50e30bd4
ZD
285 // Avoiding execution timeouts when updating
286 // a large amount of grades.
287 $progress = 0;
288 $progressbar = new \core\progress\display_if_slow();
289 $progressbar->start_html();
290 $progressbar->start_progress(get_string('savegrades', 'gradereport_singleview'), count((array) $data) - 1);
291 $changecount = array();
5c9bc966
AG
292 // This array is used to determine if the override should be excluded from being counted as a change.
293 $ignorevalues = [];
50e30bd4 294
57fac09a 295 foreach ($data as $varname => $throw) {
50e30bd4
ZD
296 $progressbar->progress($progress);
297 $progress++;
57fac09a
DW
298 if (preg_match("/(\w+)_(\d+)_(\d+)/", $varname, $matches)) {
299 $itemid = $matches[2];
300 $userid = $matches[3];
301 } else {
302 continue;
303 }
304
57fac09a
DW
305 $gradeitem = grade_item::fetch(array(
306 'id' => $itemid, 'courseid' => $this->courseid
307 ));
308
1b2c1ccb
ZD
309 if (preg_match('/^old[oe]{1}/', $varname)) {
310 $elementname = preg_replace('/^old/', '', $varname);
311 if (!isset($data->$elementname)) {
50e30bd4
ZD
312 // Decrease the progress because we've increased the
313 // size of the array we are iterating through.
314 $progress--;
1b2c1ccb
ZD
315 $data->$elementname = false;
316 }
317 }
318
319 if (!in_array($matches[1], $fields)) {
320 continue;
321 }
322
57fac09a
DW
323 if (!$gradeitem) {
324 continue;
325 }
326
327 $grade = $this->fetch_grade_or_default($gradeitem, $userid);
328
329 $classname = '\\gradereport_singleview\\local\\ui\\' . $matches[1];
330 $element = new $classname($grade);
331
332 $name = $element->get_name();
333 $oldname = "old$name";
334
335 $posted = $data->$name;
336
337 $format = $element->determine_format();
338
339 if ($format->is_textbox() and trim($data->$name) === '') {
340 $data->$name = null;
341 }
342
343 // Same value; skip.
344 if (isset($data->$oldname) && $data->$oldname == $posted) {
345 continue;
346 }
347
ae66ed23 348 // If the user submits Exclude grade elements without the proper.
43cd0e3c
TN
349 // permissions then we should refuse to update.
350 if ($matches[1] === 'exclude' && !has_capability('moodle/grade:manage', $this->context)){
351 $warnings[] = get_string('nopermissions', 'error', get_string('grade:manage', 'role'));
352 continue;
353 }
354
57fac09a 355 $msg = $element->set($posted);
5c9bc966
AG
356 // Value to check against our list of matchelements to ignore.
357 $check = explode('_', $varname, 2);
57fac09a
DW
358
359 // Optional type.
360 if (!empty($msg)) {
361 $warnings[] = $msg;
f2462522 362 if ($element instanceof \gradereport_singleview\local\ui\finalgrade) {
5c9bc966
AG
363 // Add this value to this list so that the override object that is coming next will also be skipped.
364 $ignorevalues[$check[1]] = $check[1];
f2462522
AG
365 // This item wasn't changed so don't add to the changecount.
366 continue;
367 }
57fac09a 368 }
5c9bc966
AG
369 // Check to see if this value has already been skipped.
370 if (array_key_exists($check[1], $ignorevalues)) {
371 continue;
372 }
50e30bd4
ZD
373 if (preg_match('/_(\d+)_(\d+)/', $varname, $matchelement)) {
374 $changecount[$matchelement[0]] = 1;
375 }
57fac09a
DW
376 }
377
378 // Some post-processing.
379 $eventdata = new stdClass;
380 $eventdata->warnings = $warnings;
381 $eventdata->post_data = $data;
382 $eventdata->instance = $this;
50e30bd4
ZD
383 $eventdata->changecount = $changecount;
384
385 $progressbar->end_html();
57fac09a 386
50e30bd4 387 return $eventdata;
57fac09a
DW
388 }
389
390 /**
391 * By default there are no options.
392 * @return array
393 */
394 public function options() {
395 return array();
396 }
397
398 /**
399 * Should we show the group selector?
400 * @return bool
401 */
402 public function display_group_selector() {
403 return true;
404 }
405
406 /**
407 * Should we show the next prev selector?
408 * @return bool
409 */
410 public function supports_next_prev() {
411 return true;
412 }
aac66bef
SL
413
414 /**
415 * Load a valid list of users for this gradebook as the screen "items".
416 * @return array $users A list of enroled users.
417 */
418 protected function load_users() {
419 global $CFG;
420
421 // Create a graded_users_iterator because it will properly check the groups etc.
422 $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
423 $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
424 $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $this->context);
425
426 require_once($CFG->dirroot.'/grade/lib.php');
427 $gui = new \graded_users_iterator($this->course, null, $this->groupid);
428 $gui->require_active_enrolment($showonlyactiveenrol);
429 $gui->init();
430
431 // Flatten the users.
432 $users = array();
433 while ($user = $gui->next_user()) {
434 $users[$user->user->id] = $user->user;
435 }
a938e409 436 $gui->close();
aac66bef
SL
437 return $users;
438 }
c6dd683c
DS
439
440 /**
441 * Allow selection of number of items to display per page.
442 * @return string
443 */
444 public function perpage_select() {
445 global $PAGE, $OUTPUT;
446
447 $options = array_combine(self::$validperpage, self::$validperpage);
448
449 $url = new moodle_url($PAGE->url);
450 $url->remove_params(['page', 'perpage']);
451
452 $out = '';
453 $select = new \single_select($url, 'perpage', $options, $this->perpage, null, 'perpagechanger');
454 $select->label = get_string('itemsperpage', 'gradereport_singleview');
455 $out .= $OUTPUT->render($select);
456
457 return $out;
458 }
57fac09a 459}