MDL-64161 gradereport_singleview: Single updates reported correctly.
[moodle.git] / grade / report / singleview / classes / local / screen / screen.php
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/>.
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  */
25 namespace gradereport_singleview\local\screen;
27 use context_course;
28 use moodle_url;
29 use html_writer;
30 use grade_structure;
31 use grade_grade;
32 use grade_item;
33 use stdClass;
35 defined('MOODLE_INTERNAL') || die;
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  */
44 abstract class screen {
46     /** @var int $courseid The id of the course */
47     protected $courseid;
49     /** @var int $itemid Either a user id or a grade_item id */
50     protected $itemid;
52     /** @var int $groupid The currently set groupid (if set) */
53     protected $groupid;
55     /** @var course_context $context The course context */
56     protected $context;
58     /** @var int $page The page number */
59     protected $page;
61     /** @var int $perpage Results per page */
62     protected $perpage;
64     /** @var array $items List of items on the page, they could be users or grade_items */
65     protected $items;
67     /** @var array $validperpage List of allowed values for 'perpage' setting */
68     protected static $validperpage = [20, 50, 100, 200, 400, 1000, 5000];
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;
80         $this->courseid = $courseid;
81         $this->itemid = $itemid;
82         $this->groupid = $groupid;
84         $this->context = context_course::instance($this->courseid);
85         $this->course = $DB->get_record('course', array('id' => $courseid));
87         $this->page = optional_param('page', 0, PARAM_INT);
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 {
101             $this->perpage = 100;
102         }
104         $this->init(empty($itemid));
105     }
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     }
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         ));
131         if ($display) {
132             return html_writer::link($url, $display);
133         } else {
134             return $url;
135         }
136     }
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         ));
150         if (!$grade) {
151             $default = new stdClass;
153             $default->userid = $userid;
154             $default->itemid = $item->id;
155             $default->feedback = '';
157             $grade = new grade_grade($default, false);
158         }
160         $grade->grade_item = $item;
162         return $grade;
163     }
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' => '#');
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         }
185         $all = html_writer::tag('a', get_string('all'), $attrs + array(
186             'class' => 'include all ' . $key,
187             'title' => $titleall
188         ));
190         $none = html_writer::tag('a', get_string('none'), $attrs + array(
191             'class' => 'include none ' . $key,
192             'title' => $titlenone
193         ));
195         return html_writer::tag('span', "$all / $none", array(
196             'class' => 'inclusion_links'
197         ));
198     }
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     }
211     /**
212      * Get the default heading for the screen.
213      *
214      * @return string
215      */
216     public function heading() {
217         return get_string('entrypage', 'gradereport_singleview');
218     }
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);
227     /**
228      * Get the type of items in the list.
229      *
230      * @return string
231      */
232     public abstract function item_type();
234     /**
235      * Get the entire screen as a string.
236      *
237      * @return string
238      */
239     public abstract function html();
241     /**
242      * Does this screen support paging?
243      *
244      * @return bool
245      */
246     public function supports_paging() {
247         return true;
248     }
250     /**
251      * Default pager
252      *
253      * @return string
254      */
255     public function pager() {
256         return '';
257     }
259     /**
260      * Initialise the js for this screen.
261      */
262     public function js() {
263         global $PAGE;
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         );
271         $PAGE->requires->js_init_call('M.gradereport_singleview.init', array(), false, $module);
272     }
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();
283         $fields = $this->definition();
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();
292         // This array is used to determine if the override should be excluded from being counted as a change.
293         $ignorevalues = [];
295         foreach ($data as $varname => $throw) {
296             $progressbar->progress($progress);
297             $progress++;
298             if (preg_match("/(\w+)_(\d+)_(\d+)/", $varname, $matches)) {
299                 $itemid = $matches[2];
300                 $userid = $matches[3];
301             } else {
302                 continue;
303             }
305             $gradeitem = grade_item::fetch(array(
306                 'id' => $itemid, 'courseid' => $this->courseid
307             ));
309             if (preg_match('/^old[oe]{1}/', $varname)) {
310                 $elementname = preg_replace('/^old/', '', $varname);
311                 if (!isset($data->$elementname)) {
312                     // Decrease the progress because we've increased the
313                     // size of the array we are iterating through.
314                     $progress--;
315                     $data->$elementname = false;
316                 }
317             }
319             if (!in_array($matches[1], $fields)) {
320                 continue;
321             }
323             if (!$gradeitem) {
324                 continue;
325             }
327             $grade = $this->fetch_grade_or_default($gradeitem, $userid);
329             $classname = '\\gradereport_singleview\\local\\ui\\' . $matches[1];
330             $element = new $classname($grade);
332             $name = $element->get_name();
333             $oldname = "old$name";
335             $posted = $data->$name;
337             $format = $element->determine_format();
339             if ($format->is_textbox() and trim($data->$name) === '') {
340                 $data->$name = null;
341             }
343             // Same value; skip.
344             if (isset($data->$oldname) && $data->$oldname == $posted) {
345                 continue;
346             }
348             // If the user submits Exclude grade elements without the proper.
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             }
355             $msg = $element->set($posted);
356             // Value to check against our list of matchelements to ignore.
357             $check = explode('_', $varname, 2);
359             // Optional type.
360             if (!empty($msg)) {
361                 $warnings[] = $msg;
362                 if ($element instanceof \gradereport_singleview\local\ui\finalgrade) {
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];
365                     // This item wasn't changed so don't add to the changecount.
366                     continue;
367                 }
368             }
369             // Check to see if this value has already been skipped.
370             if (array_key_exists($check[1], $ignorevalues)) {
371                 continue;
372             }
373             if (preg_match('/_(\d+)_(\d+)/', $varname, $matchelement)) {
374                 $changecount[$matchelement[0]] = 1;
375             }
376         }
378         // Some post-processing.
379         $eventdata = new stdClass;
380         $eventdata->warnings = $warnings;
381         $eventdata->post_data = $data;
382         $eventdata->instance = $this;
383         $eventdata->changecount = $changecount;
385         $progressbar->end_html();
387         return $eventdata;
388     }
390     /**
391      * By default there are no options.
392      * @return array
393      */
394     public function options() {
395         return array();
396     }
398     /**
399      * Should we show the group selector?
400      * @return bool
401      */
402     public function display_group_selector() {
403         return true;
404     }
406     /**
407      * Should we show the next prev selector?
408      * @return bool
409      */
410     public function supports_next_prev() {
411         return true;
412     }
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;
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);
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();
431         // Flatten the users.
432         $users = array();
433         while ($user = $gui->next_user()) {
434             $users[$user->user->id] = $user->user;
435         }
436         $gui->close();
437         return $users;
438     }
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;
447         $options = array_combine(self::$validperpage, self::$validperpage);
449         $url = new moodle_url($PAGE->url);
450         $url->remove_params(['page', 'perpage']);
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);
457         return $out;
458     }